@webex/internal-plugin-llm 3.11.0-next.1 → 3.11.0-next.11

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 CHANGED
@@ -23,22 +23,93 @@ npm install --save @webex/internal-plugin-llm
23
23
 
24
24
  ```js
25
25
  import '@webex/internal-plugin-llm';
26
-
27
26
  import WebexCore from '@webex/webex-core';
27
+ // Optional: import enum from package internals if needed in your app setup
28
+ // import {DataChannelTokenType} from '@webex/internal-plugin-llm/src/llm.types';
28
29
 
29
30
  const webex = new WebexCore();
30
- // locusUrl is got from meeting.locusInfo.url;
31
- // datachannelUrl is got from meeting.locusInfo.info.datachannelUrl;
32
- webex.internal.llm.registerAndConnect(locusUrl, datachannelUrl);
33
-
34
- // Checks if LLM is connected
35
- webex.internal.llm.isConnected();
36
-
37
- // Disconnect LLM connection
38
- webex.internal.llm.disconnectLLM();
39
31
 
40
- // Get Locus URL
41
- webex.internal.llm.getLocusUrl();
32
+ // locusUrl and datachannelUrl are from meeting.locusInfo
33
+ const locusUrl = meeting.locusInfo.url;
34
+ const datachannelUrl = meeting.locusInfo.info.datachannelUrl;
35
+
36
+ // Optional JWT token for data channel auth
37
+ const datachannelToken = '<jwt-token>';
38
+
39
+ // Default session (no token)
40
+ await webex.internal.llm.registerAndConnect(locusUrl, datachannelUrl);
41
+
42
+ // Default session (with JWT token)
43
+ await webex.internal.llm.registerAndConnect(locusUrl, datachannelUrl, datachannelToken);
44
+
45
+ // Multiple named sessions
46
+ await webex.internal.llm.registerAndConnect(locusUrlA, datachannelUrlA, undefined, 'session-a');
47
+ await webex.internal.llm.registerAndConnect(
48
+ locusUrlB,
49
+ datachannelUrlB,
50
+ datachannelToken,
51
+ 'session-b'
52
+ );
53
+
54
+ // Listen across multiple connections
55
+ const llm = webex.internal.llm;
56
+ const sessionA = 'session-a';
57
+ const sessionB = 'session-b';
58
+
59
+ // Default session events use the base event name.
60
+ llm.on('online', () => {
61
+ console.log('[default] connected');
62
+ });
63
+
64
+ llm.on('event', (envelope) => {
65
+ console.log('[default] event', envelope.data?.eventType, envelope.sessionId);
66
+ });
67
+
68
+ // Non-default sessions emit events with :<sessionId> suffix.
69
+ llm.on(`online:${sessionA}`, () => {
70
+ console.log(`[${sessionA}] connected`);
71
+ });
72
+
73
+ llm.on(`event:${sessionA}`, (envelope) => {
74
+ console.log(`[${sessionA}] event`, envelope.data?.eventType, envelope.sessionId);
75
+ });
76
+
77
+ llm.on(`event:${sessionB}`, (envelope) => {
78
+ console.log(`[${sessionB}] event`, envelope.data?.eventType, envelope.sessionId);
79
+ });
80
+
81
+ // Optional: store/retrieve token by token type
82
+ webex.internal.llm.setDatachannelToken(datachannelToken, 'DEFAULT');
83
+ webex.internal.llm.getDatachannelToken('DEFAULT');
84
+
85
+ // Optional: inject token refresh handler
86
+ webex.internal.llm.setRefreshHandler(async () => {
87
+ // Return shape must match plugin expectation
88
+ return {
89
+ body: {
90
+ datachannelToken: '<refreshed-jwt-token>',
91
+ datachannelTokenType: 'DEFAULT',
92
+ },
93
+ };
94
+ });
95
+
96
+ // Optional: manually trigger refresh (if needed by your flow)
97
+ await webex.internal.llm.refreshDataChannelToken();
98
+
99
+ // Per-session status and metadata
100
+ webex.internal.llm.isConnected('session-a');
101
+ webex.internal.llm.getBinding('session-a');
102
+ webex.internal.llm.getLocusUrl('session-a');
103
+ webex.internal.llm.getDatachannelUrl('session-a');
104
+
105
+ // All active sessions
106
+ webex.internal.llm.getAllConnections();
107
+
108
+ // Disconnect one session
109
+ await webex.internal.llm.disconnectLLM({code: 1000, reason: 'done'}, 'session-a');
110
+
111
+ // Disconnect all sessions
112
+ await webex.internal.llm.disconnectAllLLM({code: 1000, reason: 'shutdown'});
42
113
  ```
43
114
 
44
115
  ## Maintainers
package/dist/constants.js CHANGED
@@ -4,7 +4,9 @@ var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/defi
4
4
  _Object$defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.LLM = void 0;
7
+ exports.LLM_DEFAULT_SESSION = exports.LLM = exports.DATA_CHANNEL_WITH_JWT_TOKEN = void 0;
8
8
  // eslint-disable-next-line import/prefer-default-export
9
9
  var LLM = exports.LLM = 'llm';
10
+ var LLM_DEFAULT_SESSION = exports.LLM_DEFAULT_SESSION = 'llm-default-session';
11
+ var DATA_CHANNEL_WITH_JWT_TOKEN = exports.DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';
10
12
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["LLM","exports"],"sources":["constants.ts"],"sourcesContent":["// eslint-disable-next-line import/prefer-default-export\nexport const LLM = 'llm';\n"],"mappings":";;;;;;;AAAA;AACO,IAAMA,GAAG,GAAAC,OAAA,CAAAD,GAAA,GAAG,KAAK","ignoreList":[]}
1
+ {"version":3,"names":["LLM","exports","LLM_DEFAULT_SESSION","DATA_CHANNEL_WITH_JWT_TOKEN"],"sources":["constants.ts"],"sourcesContent":["// eslint-disable-next-line import/prefer-default-export\nexport const LLM = 'llm';\n\nexport const LLM_DEFAULT_SESSION = 'llm-default-session';\n\nexport const DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';\n"],"mappings":";;;;;;;AAAA;AACO,IAAMA,GAAG,GAAAC,OAAA,CAAAD,GAAA,GAAG,KAAK;AAEjB,IAAME,mBAAmB,GAAAD,OAAA,CAAAC,mBAAA,GAAG,qBAAqB;AAEjD,IAAMC,2BAA2B,GAAAF,OAAA,CAAAE,2BAAA,GAAG,6BAA6B","ignoreList":[]}
package/dist/index.js CHANGED
@@ -7,6 +7,12 @@ var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/o
7
7
  _Object$defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
10
+ _Object$defineProperty(exports, "DataChannelTokenType", {
11
+ enumerable: true,
12
+ get: function get() {
13
+ return _llm2.DataChannelTokenType;
14
+ }
15
+ });
10
16
  _Object$defineProperty(exports, "default", {
11
17
  enumerable: true,
12
18
  get: function get() {
@@ -15,6 +21,7 @@ _Object$defineProperty(exports, "default", {
15
21
  });
16
22
  var WebexCore = _interopRequireWildcard(require("@webex/webex-core"));
17
23
  var _llm = _interopRequireWildcard(require("./llm"));
24
+ var _llm2 = require("./llm.types");
18
25
  function _interopRequireWildcard(e, t) { if ("function" == typeof _WeakMap) var r = new _WeakMap(), n = new _WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = _Object$defineProperty) && _Object$getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
19
26
  WebexCore.registerInternalPlugin('llm', _llm.default, {
20
27
  config: _llm.config
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["WebexCore","_interopRequireWildcard","require","_llm","e","t","_WeakMap","r","n","__esModule","o","i","f","__proto__","default","_typeof","has","get","set","_t","hasOwnProperty","call","_Object$defineProperty","_Object$getOwnPropertyDescriptor","registerInternalPlugin","LLMChannel","config"],"sources":["index.ts"],"sourcesContent":["import * as WebexCore from '@webex/webex-core';\nimport LLMChannel, {config} from './llm';\n\nWebexCore.registerInternalPlugin('llm', LLMChannel, {\n config,\n});\n\nexport {default} from './llm';\n"],"mappings":";;;;;;;;;;;;;;;AAAA,IAAAA,SAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,IAAA,GAAAF,uBAAA,CAAAC,OAAA;AAAyC,SAAAD,wBAAAG,CAAA,EAAAC,CAAA,6BAAAC,QAAA,MAAAC,CAAA,OAAAD,QAAA,IAAAE,CAAA,OAAAF,QAAA,YAAAL,uBAAA,YAAAA,wBAAAG,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,gBAAAW,OAAA,CAAAX,CAAA,0BAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAM,GAAA,CAAAZ,CAAA,UAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,GAAAM,CAAA,CAAAQ,GAAA,CAAAd,CAAA,EAAAQ,CAAA,cAAAO,EAAA,IAAAf,CAAA,gBAAAe,EAAA,OAAAC,cAAA,CAAAC,IAAA,CAAAjB,CAAA,EAAAe,EAAA,OAAAR,CAAA,IAAAD,CAAA,GAAAY,sBAAA,KAAAC,gCAAA,CAAAnB,CAAA,EAAAe,EAAA,OAAAR,CAAA,CAAAM,GAAA,IAAAN,CAAA,CAAAO,GAAA,IAAAR,CAAA,CAAAE,CAAA,EAAAO,EAAA,EAAAR,CAAA,IAAAC,CAAA,CAAAO,EAAA,IAAAf,CAAA,CAAAe,EAAA,WAAAP,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAEzCL,SAAS,CAACwB,sBAAsB,CAAC,KAAK,EAAEC,YAAU,EAAE;EAClDC,MAAM,EAANA;AACF,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["WebexCore","_interopRequireWildcard","require","_llm","_llm2","e","t","_WeakMap","r","n","__esModule","o","i","f","__proto__","default","_typeof","has","get","set","_t","hasOwnProperty","call","_Object$defineProperty","_Object$getOwnPropertyDescriptor","registerInternalPlugin","LLMChannel","config"],"sources":["index.ts"],"sourcesContent":["import * as WebexCore from '@webex/webex-core';\nimport LLMChannel, {config} from './llm';\nimport {DataChannelTokenType} from './llm.types';\n\nWebexCore.registerInternalPlugin('llm', LLMChannel, {\n config,\n});\n\nexport {DataChannelTokenType};\nexport {default} from './llm';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,SAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,IAAA,GAAAF,uBAAA,CAAAC,OAAA;AACA,IAAAE,KAAA,GAAAF,OAAA;AAAiD,SAAAD,wBAAAI,CAAA,EAAAC,CAAA,6BAAAC,QAAA,MAAAC,CAAA,OAAAD,QAAA,IAAAE,CAAA,OAAAF,QAAA,YAAAN,uBAAA,YAAAA,wBAAAI,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,gBAAAW,OAAA,CAAAX,CAAA,0BAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAM,GAAA,CAAAZ,CAAA,UAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,GAAAM,CAAA,CAAAQ,GAAA,CAAAd,CAAA,EAAAQ,CAAA,cAAAO,EAAA,IAAAf,CAAA,gBAAAe,EAAA,OAAAC,cAAA,CAAAC,IAAA,CAAAjB,CAAA,EAAAe,EAAA,OAAAR,CAAA,IAAAD,CAAA,GAAAY,sBAAA,KAAAC,gCAAA,CAAAnB,CAAA,EAAAe,EAAA,OAAAR,CAAA,CAAAM,GAAA,IAAAN,CAAA,CAAAO,GAAA,IAAAR,CAAA,CAAAE,CAAA,EAAAO,EAAA,EAAAR,CAAA,IAAAC,CAAA,CAAAO,EAAA,IAAAf,CAAA,CAAAe,EAAA,WAAAP,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAEjDN,SAAS,CAACyB,sBAAsB,CAAC,KAAK,EAAEC,YAAU,EAAE;EAClDC,MAAM,EAANA;AACF,CAAC,CAAC","ignoreList":[]}
package/dist/llm.js CHANGED
@@ -7,18 +7,20 @@ _Object$defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
9
  exports.default = exports.config = void 0;
10
- var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
10
+ var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
11
+ var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map"));
12
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));
11
13
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
14
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
12
15
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/possibleConstructorReturn"));
13
16
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/getPrototypeOf"));
14
17
  var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/inherits"));
15
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
18
+ var _defineProperty3 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
16
19
  var _internalPluginMercury = _interopRequireDefault(require("@webex/internal-plugin-mercury"));
17
20
  var _constants = require("./constants");
21
+ var _llm = require("./llm.types");
18
22
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? _Reflect$construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
19
- function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /* eslint-disable consistent-return */
20
- // eslint-disable-next-line no-unused-vars
21
-
23
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /* eslint-disable consistent-return */ // eslint-disable-next-line no-unused-vars
22
24
  var config = exports.config = {
23
25
  llm: {
24
26
  /**
@@ -61,95 +63,246 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
61
63
  args[_key] = arguments[_key];
62
64
  }
63
65
  _this = _callSuper(this, LLMChannel, [].concat(args));
64
- (0, _defineProperty2.default)(_this, "namespace", _constants.LLM);
66
+ (0, _defineProperty3.default)(_this, "namespace", _constants.LLM);
67
+ (0, _defineProperty3.default)(_this, "defaultSessionId", _constants.LLM_DEFAULT_SESSION);
65
68
  /**
66
- * If the LLM plugin has been registered and listening
67
- * @instance
68
- * @type {Boolean}
69
- * @public
69
+ * Map to store connection-specific data for multiple LLM connections
70
+ * @private
71
+ * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}
70
72
  */
71
- (0, _defineProperty2.default)(_this, "webSocketUrl", void 0);
72
- (0, _defineProperty2.default)(_this, "binding", void 0);
73
- (0, _defineProperty2.default)(_this, "locusUrl", void 0);
74
- (0, _defineProperty2.default)(_this, "datachannelUrl", void 0);
73
+ (0, _defineProperty3.default)(_this, "connections", new _map.default());
74
+ (0, _defineProperty3.default)(_this, "datachannelTokens", (0, _defineProperty3.default)((0, _defineProperty3.default)({}, _llm.DataChannelTokenType.Default, undefined), _llm.DataChannelTokenType.PracticeSession, undefined));
75
+ (0, _defineProperty3.default)(_this, "refreshHandler", void 0);
75
76
  /**
76
77
  * Register to the websocket
77
78
  * @param {string} llmSocketUrl
79
+ * @param {string} datachannelToken
80
+ * @param {string} sessionId - Connection identifier
78
81
  * @returns {Promise<void>}
79
82
  */
80
- (0, _defineProperty2.default)(_this, "register", function (llmSocketUrl) {
81
- return _this.request({
82
- method: 'POST',
83
- url: llmSocketUrl,
84
- body: {
85
- deviceUrl: _this.webex.internal.device.url
86
- }
87
- }).then(function (res) {
88
- _this.webSocketUrl = res.body.webSocketUrl;
89
- _this.binding = res.body.binding;
90
- }).catch(function (error) {
91
- _this.logger.error("Error connecting to websocket: ".concat(error));
92
- throw error;
93
- });
94
- });
83
+ (0, _defineProperty3.default)(_this, "register", /*#__PURE__*/function () {
84
+ var _ref2 = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee(llmSocketUrl, datachannelToken) {
85
+ var sessionId,
86
+ isDataChannelTokenEnabled,
87
+ _args = arguments;
88
+ return _regenerator.default.wrap(function (_context) {
89
+ while (1) switch (_context.prev = _context.next) {
90
+ case 0:
91
+ sessionId = _args.length > 2 && _args[2] !== undefined ? _args[2] : _constants.LLM_DEFAULT_SESSION;
92
+ _context.next = 1;
93
+ return _this.isDataChannelTokenEnabled();
94
+ case 1:
95
+ isDataChannelTokenEnabled = _context.sent;
96
+ return _context.abrupt("return", _this.request({
97
+ method: 'POST',
98
+ url: llmSocketUrl,
99
+ body: {
100
+ deviceUrl: _this.webex.internal.device.url
101
+ },
102
+ headers: isDataChannelTokenEnabled && datachannelToken ? {
103
+ 'Data-Channel-Auth-Token': datachannelToken
104
+ } : {}
105
+ }).then(function (res) {
106
+ // Get or create connection data
107
+ var sessionData = _this.connections.get(sessionId) || {};
108
+ sessionData.webSocketUrl = res.body.webSocketUrl;
109
+ sessionData.binding = res.body.binding;
110
+ _this.connections.set(sessionId, sessionData);
111
+ }).catch(function (error) {
112
+ _this.logger.error("Error connecting to websocket for ".concat(sessionId, ": ").concat(error));
113
+ throw error;
114
+ }));
115
+ case 2:
116
+ case "end":
117
+ return _context.stop();
118
+ }
119
+ }, _callee);
120
+ }));
121
+ return function (_x, _x2) {
122
+ return _ref2.apply(this, arguments);
123
+ };
124
+ }());
95
125
  /**
96
126
  * Register and connect to the websocket
97
127
  * @param {string} locusUrl
98
128
  * @param {string} datachannelUrl
129
+ * @param {string} datachannelToken
130
+ * @param {string} sessionId - Connection identifier
99
131
  * @returns {Promise<void>}
100
132
  */
101
- (0, _defineProperty2.default)(_this, "registerAndConnect", function (locusUrl, datachannelUrl) {
102
- return _this.register(datachannelUrl).then(function () {
133
+ (0, _defineProperty3.default)(_this, "registerAndConnect", function (locusUrl, datachannelUrl, datachannelToken) {
134
+ var sessionId = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _constants.LLM_DEFAULT_SESSION;
135
+ return _this.register(datachannelUrl, datachannelToken, sessionId).then(function () {
103
136
  if (!locusUrl || !datachannelUrl) return undefined;
104
- _this.locusUrl = locusUrl;
105
- _this.datachannelUrl = datachannelUrl;
106
- _this.connect(_this.webSocketUrl);
137
+
138
+ // Get or create connection data
139
+ var sessionData = _this.connections.get(sessionId) || {};
140
+ sessionData.locusUrl = locusUrl;
141
+ sessionData.datachannelUrl = datachannelUrl;
142
+ sessionData.datachannelToken = datachannelToken;
143
+ _this.connections.set(sessionId, sessionData);
144
+ return _this.connect(sessionData.webSocketUrl, sessionId);
107
145
  });
108
146
  });
109
147
  /**
110
148
  * Tells if LLM socket is connected
149
+ * @param {string} sessionId - Connection identifier
111
150
  * @returns {boolean} connected
112
151
  */
113
- (0, _defineProperty2.default)(_this, "isConnected", function () {
114
- return _this.connected;
152
+ (0, _defineProperty3.default)(_this, "isConnected", function () {
153
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _constants.LLM_DEFAULT_SESSION;
154
+ var socket = _this.getSocket(sessionId);
155
+ return socket ? socket.connected : false;
115
156
  });
116
157
  /**
117
158
  * Tells if LLM socket is binding
159
+ * @param {string} sessionId - Connection identifier
118
160
  * @returns {string} binding
119
161
  */
120
- (0, _defineProperty2.default)(_this, "getBinding", function () {
121
- return _this.binding;
162
+ (0, _defineProperty3.default)(_this, "getBinding", function () {
163
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _constants.LLM_DEFAULT_SESSION;
164
+ var sessionData = _this.connections.get(sessionId);
165
+ return sessionData === null || sessionData === void 0 ? void 0 : sessionData.binding;
122
166
  });
123
167
  /**
124
168
  * Get Locus URL for the connection
169
+ * @param {string} sessionId - Connection identifier
125
170
  * @returns {string} locus Url
126
171
  */
127
- (0, _defineProperty2.default)(_this, "getLocusUrl", function () {
128
- return _this.locusUrl;
172
+ (0, _defineProperty3.default)(_this, "getLocusUrl", function () {
173
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _constants.LLM_DEFAULT_SESSION;
174
+ var sessionData = _this.connections.get(sessionId);
175
+ return sessionData === null || sessionData === void 0 ? void 0 : sessionData.locusUrl;
129
176
  });
130
177
  /**
131
178
  * Get data channel URL for the connection
179
+ * @param {string} sessionId - Connection identifier
132
180
  * @returns {string} data channel Url
133
181
  */
134
- (0, _defineProperty2.default)(_this, "getDatachannelUrl", function () {
135
- return _this.datachannelUrl;
182
+ (0, _defineProperty3.default)(_this, "getDatachannelUrl", function () {
183
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _constants.LLM_DEFAULT_SESSION;
184
+ var sessionData = _this.connections.get(sessionId);
185
+ return sessionData === null || sessionData === void 0 ? void 0 : sessionData.datachannelUrl;
186
+ });
187
+ /**
188
+ * Get data channel token for the connection
189
+ * @param {DataChannelTokenType} dataChannelTokenType
190
+ * @returns {string} data channel token
191
+ */
192
+ (0, _defineProperty3.default)(_this, "getDatachannelToken", function (dataChannelTokenType) {
193
+ return _this.datachannelTokens[dataChannelTokenType];
194
+ });
195
+ /**
196
+ * Set data channel token for the connection
197
+ * @param {string} datachannelToken - data channel token
198
+ * @param {DataChannelTokenType} dataChannelTokenType
199
+ * @returns {void}
200
+ */
201
+ (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken, dataChannelTokenType) {
202
+ _this.datachannelTokens[dataChannelTokenType] = datachannelToken;
136
203
  });
137
204
  /**
138
205
  * Disconnects websocket connection
139
206
  * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
207
+ * @param {string} sessionId - Connection identifier
208
+ * @returns {Promise<void>}
209
+ */
210
+ (0, _defineProperty3.default)(_this, "disconnectLLM", function (options) {
211
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.LLM_DEFAULT_SESSION;
212
+ return _this.disconnect(options, sessionId).then(function () {
213
+ // Clean up sessions data
214
+ _this.connections.delete(sessionId);
215
+ });
216
+ });
217
+ /**
218
+ * Disconnects all LLM websocket connections
219
+ * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
140
220
  * @returns {Promise<void>}
141
221
  */
142
- (0, _defineProperty2.default)(_this, "disconnectLLM", function (options) {
143
- return _this.disconnect(options).then(function () {
144
- _this.locusUrl = undefined;
145
- _this.datachannelUrl = undefined;
146
- _this.binding = undefined;
147
- _this.webSocketUrl = undefined;
222
+ (0, _defineProperty3.default)(_this, "disconnectAllLLM", function (options) {
223
+ return _this.disconnectAll(options).then(function () {
224
+ // Clean up all connection data
225
+ _this.connections.clear();
148
226
  });
149
227
  });
228
+ /**
229
+ * Get all active LLM connections
230
+ * @returns {Map} Map of sessionId to session data
231
+ */
232
+ (0, _defineProperty3.default)(_this, "getAllConnections", function () {
233
+ return new _map.default(_this.connections);
234
+ });
150
235
  return _this;
151
236
  }
152
237
  (0, _inherits2.default)(LLMChannel, _ref);
153
- return (0, _createClass2.default)(LLMChannel);
238
+ return (0, _createClass2.default)(LLMChannel, [{
239
+ key: "setRefreshHandler",
240
+ value:
241
+ /**
242
+ * Set the handler used to refresh the DataChannel token
243
+ *
244
+ * @param {function} handler - Function that returns a refreshed token
245
+ * @returns {void}
246
+ */
247
+ function setRefreshHandler(handler) {
248
+ this.refreshHandler = handler;
249
+ }
250
+
251
+ /**
252
+ * Refresh the data channel token using the injected handler.
253
+ * Logs a descriptive error if the handler is missing or fails.
254
+ *
255
+ * @returns {Promise<string>} The refreshed token.
256
+ */
257
+ }, {
258
+ key: "refreshDataChannelToken",
259
+ value: (function () {
260
+ var _refreshDataChannelToken = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
261
+ var error, res, _t;
262
+ return _regenerator.default.wrap(function (_context2) {
263
+ while (1) switch (_context2.prev = _context2.next) {
264
+ case 0:
265
+ if (this.refreshHandler) {
266
+ _context2.next = 1;
267
+ break;
268
+ }
269
+ error = new Error('LLM refreshHandler is not set');
270
+ this.logger.error("Error refreshing DataChannel token: ".concat(error.message));
271
+ throw error;
272
+ case 1:
273
+ _context2.prev = 1;
274
+ _context2.next = 2;
275
+ return this.refreshHandler();
276
+ case 2:
277
+ res = _context2.sent;
278
+ return _context2.abrupt("return", res);
279
+ case 3:
280
+ _context2.prev = 3;
281
+ _t = _context2["catch"](1);
282
+ this.logger.error("Error refreshing DataChannel token: ".concat(_t));
283
+ throw _t;
284
+ case 4:
285
+ case "end":
286
+ return _context2.stop();
287
+ }
288
+ }, _callee2, this, [[1, 3]]);
289
+ }));
290
+ function refreshDataChannelToken() {
291
+ return _refreshDataChannelToken.apply(this, arguments);
292
+ }
293
+ return refreshDataChannelToken;
294
+ }())
295
+ }, {
296
+ key: "isDataChannelTokenEnabled",
297
+ value:
298
+ /**
299
+ * Returns true if data channel token is enabled, false otherwise
300
+ * @returns {Promise<boolean>} resolves with true if data channel token is enabled
301
+ */
302
+ function isDataChannelTokenEnabled() {
303
+ // @ts-ignore
304
+ return this.webex.internal.feature.getFeature('developer', _constants.DATA_CHANNEL_WITH_JWT_TOKEN);
305
+ }
306
+ }]);
154
307
  }(_internalPluginMercury.default);
155
308
  //# sourceMappingURL=llm.js.map
package/dist/llm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["_internalPluginMercury","_interopRequireDefault","require","_constants","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","_Reflect$construct","constructor","apply","Boolean","prototype","valueOf","call","config","exports","llm","pingInterval","process","env","MERCURY_PING_INTERVAL","pongTimeout","MERCURY_PONG_TIMEOUT","backoffTimeMax","MERCURY_BACKOFF_TIME_MAX","backoffTimeReset","MERCURY_BACKOFF_TIME_RESET","forceCloseDelay","MERCURY_FORCE_CLOSE_DELAY","LLMChannel","_ref","_this","_classCallCheck2","_len","arguments","length","args","Array","_key","concat","_defineProperty2","LLM","llmSocketUrl","request","method","url","body","deviceUrl","webex","internal","device","then","res","webSocketUrl","binding","catch","error","logger","locusUrl","datachannelUrl","register","undefined","connect","connected","options","disconnect","_inherits2","_createClass2","Mercury"],"sources":["llm.ts"],"sourcesContent":["/* eslint-disable consistent-return */\n\nimport Mercury from '@webex/internal-plugin-mercury';\n\nimport {LLM} from './constants';\n// eslint-disable-next-line no-unused-vars\nimport {ILLMChannel} from './llm.types';\n\nexport const config = {\n llm: {\n /**\n * Milliseconds between pings sent up the socket\n * @type {number}\n */\n pingInterval: process.env.MERCURY_PING_INTERVAL || 15000,\n /**\n * Milliseconds to wait for a pong before declaring the connection dead\n * @type {number}\n */\n pongTimeout: process.env.MERCURY_PONG_TIMEOUT || 14000,\n /**\n * Maximum milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeMax: process.env.MERCURY_BACKOFF_TIME_MAX || 32000,\n /**\n * Initial milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeReset: process.env.MERCURY_BACKOFF_TIME_RESET || 1000,\n /**\n * Milliseconds to wait for a close frame before declaring the socket dead and\n * discarding it\n * @type {[type]}\n */\n forceCloseDelay: process.env.MERCURY_FORCE_CLOSE_DELAY || 2000,\n },\n};\n\n/**\n * LLMChannel to provide socket connections\n */\nexport default class LLMChannel extends (Mercury as any) implements ILLMChannel {\n namespace = LLM;\n\n /**\n * If the LLM plugin has been registered and listening\n * @instance\n * @type {Boolean}\n * @public\n */\n\n private webSocketUrl?: string;\n\n private binding?: string;\n\n private locusUrl?: string;\n\n private datachannelUrl?: string;\n\n /**\n * Register to the websocket\n * @param {string} llmSocketUrl\n * @returns {Promise<void>}\n */\n private register = (llmSocketUrl: string): Promise<void> =>\n this.request({\n method: 'POST',\n url: llmSocketUrl,\n body: {deviceUrl: this.webex.internal.device.url},\n })\n .then((res: {body: {webSocketUrl: string; binding: string}}) => {\n this.webSocketUrl = res.body.webSocketUrl;\n this.binding = res.body.binding;\n })\n .catch((error: any) => {\n this.logger.error(`Error connecting to websocket: ${error}`);\n throw error;\n });\n\n /**\n * Register and connect to the websocket\n * @param {string} locusUrl\n * @param {string} datachannelUrl\n * @returns {Promise<void>}\n */\n public registerAndConnect = (locusUrl: string, datachannelUrl: string): Promise<void> =>\n this.register(datachannelUrl).then(() => {\n if (!locusUrl || !datachannelUrl) return undefined;\n this.locusUrl = locusUrl;\n this.datachannelUrl = datachannelUrl;\n this.connect(this.webSocketUrl);\n });\n\n /**\n * Tells if LLM socket is connected\n * @returns {boolean} connected\n */\n public isConnected = (): boolean => this.connected;\n\n /**\n * Tells if LLM socket is binding\n * @returns {string} binding\n */\n public getBinding = (): string => this.binding;\n\n /**\n * Get Locus URL for the connection\n * @returns {string} locus Url\n */\n public getLocusUrl = (): string => this.locusUrl;\n\n /**\n * Get data channel URL for the connection\n * @returns {string} data channel Url\n */\n public getDatachannelUrl = (): string => this.datachannelUrl;\n\n /**\n * Disconnects websocket connection\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @returns {Promise<void>}\n */\n public disconnectLLM = (options: object): Promise<void> =>\n this.disconnect(options).then(() => {\n this.locusUrl = undefined;\n this.datachannelUrl = undefined;\n this.binding = undefined;\n this.webSocketUrl = undefined;\n });\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_internalPluginMercury","_interopRequireDefault","require","_constants","_llm","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","_Reflect$construct","constructor","apply","Boolean","prototype","valueOf","call","config","exports","llm","pingInterval","process","env","MERCURY_PING_INTERVAL","pongTimeout","MERCURY_PONG_TIMEOUT","backoffTimeMax","MERCURY_BACKOFF_TIME_MAX","backoffTimeReset","MERCURY_BACKOFF_TIME_RESET","forceCloseDelay","MERCURY_FORCE_CLOSE_DELAY","LLMChannel","_ref","_this","_classCallCheck2","_len","arguments","length","args","Array","_key","concat","_defineProperty3","LLM","LLM_DEFAULT_SESSION","_map","DataChannelTokenType","Default","undefined","PracticeSession","_ref2","_asyncToGenerator2","_regenerator","mark","_callee","llmSocketUrl","datachannelToken","sessionId","isDataChannelTokenEnabled","_args","wrap","_context","prev","next","sent","abrupt","request","method","url","body","deviceUrl","webex","internal","device","headers","then","res","sessionData","connections","get","webSocketUrl","binding","set","catch","error","logger","stop","_x","_x2","locusUrl","datachannelUrl","register","connect","socket","getSocket","connected","dataChannelTokenType","datachannelTokens","options","disconnect","delete","disconnectAll","clear","_inherits2","_createClass2","key","value","setRefreshHandler","handler","refreshHandler","_refreshDataChannelToken","_callee2","_t","_context2","Error","message","refreshDataChannelToken","feature","getFeature","DATA_CHANNEL_WITH_JWT_TOKEN","Mercury"],"sources":["llm.ts"],"sourcesContent":["/* eslint-disable consistent-return */\n\nimport Mercury from '@webex/internal-plugin-mercury';\n\nimport {LLM, DATA_CHANNEL_WITH_JWT_TOKEN, LLM_DEFAULT_SESSION} from './constants';\n// eslint-disable-next-line no-unused-vars\nimport {ILLMChannel, DataChannelTokenType} from './llm.types';\n\nexport const config = {\n llm: {\n /**\n * Milliseconds between pings sent up the socket\n * @type {number}\n */\n pingInterval: process.env.MERCURY_PING_INTERVAL || 15000,\n /**\n * Milliseconds to wait for a pong before declaring the connection dead\n * @type {number}\n */\n pongTimeout: process.env.MERCURY_PONG_TIMEOUT || 14000,\n /**\n * Maximum milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeMax: process.env.MERCURY_BACKOFF_TIME_MAX || 32000,\n /**\n * Initial milliseconds between connection attempts\n * @type {Number}\n */\n backoffTimeReset: process.env.MERCURY_BACKOFF_TIME_RESET || 1000,\n /**\n * Milliseconds to wait for a close frame before declaring the socket dead and\n * discarding it\n * @type {[type]}\n */\n forceCloseDelay: process.env.MERCURY_FORCE_CLOSE_DELAY || 2000,\n },\n};\n\n/**\n * LLMChannel to provide socket connections\n */\nexport default class LLMChannel extends (Mercury as any) implements ILLMChannel {\n namespace = LLM;\n defaultSessionId = LLM_DEFAULT_SESSION;\n /**\n * Map to store connection-specific data for multiple LLM connections\n * @private\n * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}\n */\n private connections: Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n > = new Map();\n\n private datachannelTokens: Record<DataChannelTokenType, string> = {\n [DataChannelTokenType.Default]: undefined,\n [DataChannelTokenType.PracticeSession]: undefined,\n };\n\n private refreshHandler?: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>;\n\n /**\n * Register to the websocket\n * @param {string} llmSocketUrl\n * @param {string} datachannelToken\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n private register = async (\n llmSocketUrl: string,\n datachannelToken?: string,\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> => {\n const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();\n\n return this.request({\n method: 'POST',\n url: llmSocketUrl,\n body: {deviceUrl: this.webex.internal.device.url},\n headers:\n isDataChannelTokenEnabled && datachannelToken\n ? {'Data-Channel-Auth-Token': datachannelToken}\n : {},\n })\n .then((res: {body: {webSocketUrl: string; binding: string}}) => {\n // Get or create connection data\n const sessionData = this.connections.get(sessionId) || {};\n sessionData.webSocketUrl = res.body.webSocketUrl;\n sessionData.binding = res.body.binding;\n this.connections.set(sessionId, sessionData);\n })\n .catch((error: any) => {\n this.logger.error(`Error connecting to websocket for ${sessionId}: ${error}`);\n throw error;\n });\n };\n\n /**\n * Register and connect to the websocket\n * @param {string} locusUrl\n * @param {string} datachannelUrl\n * @param {string} datachannelToken\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n public registerAndConnect = (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string,\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> =>\n this.register(datachannelUrl, datachannelToken, sessionId).then(() => {\n if (!locusUrl || !datachannelUrl) return undefined;\n\n // Get or create connection data\n const sessionData = this.connections.get(sessionId) || {};\n sessionData.locusUrl = locusUrl;\n sessionData.datachannelUrl = datachannelUrl;\n sessionData.datachannelToken = datachannelToken;\n this.connections.set(sessionId, sessionData);\n\n return this.connect(sessionData.webSocketUrl, sessionId);\n });\n\n /**\n * Tells if LLM socket is connected\n * @param {string} sessionId - Connection identifier\n * @returns {boolean} connected\n */\n public isConnected = (sessionId = LLM_DEFAULT_SESSION): boolean => {\n const socket = this.getSocket(sessionId);\n\n return socket ? socket.connected : false;\n };\n\n /**\n * Tells if LLM socket is binding\n * @param {string} sessionId - Connection identifier\n * @returns {string} binding\n */\n public getBinding = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.binding;\n };\n\n /**\n * Get Locus URL for the connection\n * @param {string} sessionId - Connection identifier\n * @returns {string} locus Url\n */\n public getLocusUrl = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.locusUrl;\n };\n\n /**\n * Get data channel URL for the connection\n * @param {string} sessionId - Connection identifier\n * @returns {string} data channel Url\n */\n public getDatachannelUrl = (sessionId = LLM_DEFAULT_SESSION): string => {\n const sessionData = this.connections.get(sessionId);\n\n return sessionData?.datachannelUrl;\n };\n\n /**\n * Get data channel token for the connection\n * @param {DataChannelTokenType} dataChannelTokenType\n * @returns {string} data channel token\n */\n public getDatachannelToken = (dataChannelTokenType: DataChannelTokenType): string => {\n return this.datachannelTokens[dataChannelTokenType];\n };\n\n /**\n * Set data channel token for the connection\n * @param {string} datachannelToken - data channel token\n * @param {DataChannelTokenType} dataChannelTokenType\n * @returns {void}\n */\n public setDatachannelToken = (\n datachannelToken: string,\n dataChannelTokenType: DataChannelTokenType\n ): void => {\n this.datachannelTokens[dataChannelTokenType] = datachannelToken;\n };\n\n /**\n * Set the handler used to refresh the DataChannel token\n *\n * @param {function} handler - Function that returns a refreshed token\n * @returns {void}\n */\n public setRefreshHandler(\n handler: () => Promise<{\n body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};\n }>\n ) {\n this.refreshHandler = handler;\n }\n\n /**\n * Refresh the data channel token using the injected handler.\n * Logs a descriptive error if the handler is missing or fails.\n *\n * @returns {Promise<string>} The refreshed token.\n */\n public async refreshDataChannelToken() {\n if (!this.refreshHandler) {\n const error = new Error('LLM refreshHandler is not set');\n this.logger.error(`Error refreshing DataChannel token: ${error.message}`);\n throw error;\n }\n\n try {\n const res = await this.refreshHandler();\n\n return res;\n } catch (error: any) {\n this.logger.error(`Error refreshing DataChannel token: ${error}`);\n throw error;\n }\n }\n\n /**\n * Disconnects websocket connection\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @param {string} sessionId - Connection identifier\n * @returns {Promise<void>}\n */\n public disconnectLLM = (\n options: {code: number; reason: string},\n sessionId: string = LLM_DEFAULT_SESSION\n ): Promise<void> =>\n this.disconnect(options, sessionId).then(() => {\n // Clean up sessions data\n this.connections.delete(sessionId);\n });\n\n /**\n * Disconnects all LLM websocket connections\n * @param {{code: number, reason: string}} options - The disconnect option object with code and reason\n * @returns {Promise<void>}\n */\n public disconnectAllLLM = (options?: {code: number; reason: string}): Promise<void> =>\n this.disconnectAll(options).then(() => {\n // Clean up all connection data\n this.connections.clear();\n });\n\n /**\n * Get all active LLM connections\n * @returns {Map} Map of sessionId to session data\n */\n public getAllConnections = (): Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n > => new Map(this.connections);\n\n /**\n * Returns true if data channel token is enabled, false otherwise\n * @returns {Promise<boolean>} resolves with true if data channel token is enabled\n */\n public isDataChannelTokenEnabled(): Promise<boolean> {\n // @ts-ignore\n return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);\n }\n}\n"],"mappings":"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","ignoreList":[]}
package/dist/llm.types.js CHANGED
@@ -4,4 +4,10 @@ var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/defi
4
4
  _Object$defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
+ exports.DataChannelTokenType = void 0;
8
+ var DataChannelTokenType = exports.DataChannelTokenType = /*#__PURE__*/function (DataChannelTokenType) {
9
+ DataChannelTokenType["Default"] = "default";
10
+ DataChannelTokenType["PracticeSession"] = "practiceSession";
11
+ return DataChannelTokenType;
12
+ }({}); // eslint-disable-next-line import/prefer-default-export
7
13
  //# sourceMappingURL=llm.types.js.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["llm.types.ts"],"sourcesContent":["interface ILLMChannel {\n registerAndConnect: (locusUrl: string, datachannelUrl: string) => Promise<void>;\n isConnected: () => boolean;\n getBinding: () => string;\n getLocusUrl: () => string;\n disconnectLLM: (options: {code: number; reason: string}) => Promise<void>;\n}\n// eslint-disable-next-line import/prefer-default-export\nexport type {ILLMChannel};\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"names":["DataChannelTokenType","exports"],"sources":["llm.types.ts"],"sourcesContent":["interface ILLMChannel {\n registerAndConnect: (\n locusUrl: string,\n datachannelUrl: string,\n datachannelToken?: string,\n sessionId?: string\n ) => Promise<void>;\n isConnected: (sessionId?: string) => boolean;\n getBinding: (sessionId?: string) => string;\n getLocusUrl: (sessionId?: string) => string;\n getDatachannelUrl: (sessionId?: string) => string;\n disconnectLLM: (options: {code: number; reason: string}, sessionId?: string) => Promise<void>;\n disconnectAllLLM: (options?: {code: number; reason: string}) => Promise<void>;\n getAllConnections: () => Map<\n string,\n {\n webSocketUrl?: string;\n binding?: string;\n locusUrl?: string;\n datachannelUrl?: string;\n datachannelToken?: string;\n }\n >;\n}\n\nexport enum DataChannelTokenType {\n Default = 'default',\n PracticeSession = 'practiceSession',\n}\n\n// eslint-disable-next-line import/prefer-default-export\nexport type {ILLMChannel};\n"],"mappings":";;;;;;;IAyBYA,oBAAoB,GAAAC,OAAA,CAAAD,oBAAA,0BAApBA,oBAAoB;EAApBA,oBAAoB;EAApBA,oBAAoB;EAAA,OAApBA,oBAAoB;AAAA,OAKhC","ignoreList":[]}
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "node": ">=18"
14
14
  },
15
15
  "dependencies": {
16
- "@webex/internal-plugin-mercury": "3.11.0-next.1"
16
+ "@webex/internal-plugin-mercury": "3.11.0-next.10"
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.10.0-next.1",
31
- "@webex/test-helper-mocha": "3.10.0-next.1",
32
- "@webex/test-helper-mock-webex": "3.10.0-next.1",
33
- "@webex/test-helper-test-users": "3.10.0-next.1",
30
+ "@webex/test-helper-chai": "3.11.0-next.1",
31
+ "@webex/test-helper-mocha": "3.11.0-next.1",
32
+ "@webex/test-helper-mock-webex": "3.11.0-next.1",
33
+ "@webex/test-helper-test-users": "3.11.0-next.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.11.0-next.1"
47
+ "version": "3.11.0-next.11"
48
48
  }
package/src/constants.ts CHANGED
@@ -1,2 +1,6 @@
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';
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import * as WebexCore from '@webex/webex-core';
2
2
  import LLMChannel, {config} from './llm';
3
+ import {DataChannelTokenType} from './llm.types';
3
4
 
4
5
  WebexCore.registerInternalPlugin('llm', LLMChannel, {
5
6
  config,
6
7
  });
7
8
 
9
+ export {DataChannelTokenType};
8
10
  export {default} from './llm';
package/src/llm.ts CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  import Mercury from '@webex/internal-plugin-mercury';
4
4
 
5
- import {LLM} from './constants';
5
+ import {LLM, DATA_CHANNEL_WITH_JWT_TOKEN, LLM_DEFAULT_SESSION} from './constants';
6
6
  // eslint-disable-next-line no-unused-vars
7
- import {ILLMChannel} from './llm.types';
7
+ import {ILLMChannel, DataChannelTokenType} from './llm.types';
8
8
 
9
9
  export const config = {
10
10
  llm: {
@@ -42,90 +42,245 @@ export const config = {
42
42
  */
43
43
  export default class LLMChannel extends (Mercury as any) implements ILLMChannel {
44
44
  namespace = LLM;
45
-
45
+ defaultSessionId = LLM_DEFAULT_SESSION;
46
46
  /**
47
- * If the LLM plugin has been registered and listening
48
- * @instance
49
- * @type {Boolean}
50
- * @public
47
+ * Map to store connection-specific data for multiple LLM connections
48
+ * @private
49
+ * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}
51
50
  */
51
+ private connections: Map<
52
+ string,
53
+ {
54
+ webSocketUrl?: string;
55
+ binding?: string;
56
+ locusUrl?: string;
57
+ datachannelUrl?: string;
58
+ datachannelToken?: string;
59
+ }
60
+ > = new Map();
52
61
 
53
- private webSocketUrl?: string;
54
-
55
- private binding?: string;
56
-
57
- private locusUrl?: string;
62
+ private datachannelTokens: Record<DataChannelTokenType, string> = {
63
+ [DataChannelTokenType.Default]: undefined,
64
+ [DataChannelTokenType.PracticeSession]: undefined,
65
+ };
58
66
 
59
- private datachannelUrl?: string;
67
+ private refreshHandler?: () => Promise<{
68
+ body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
69
+ }>;
60
70
 
61
71
  /**
62
72
  * Register to the websocket
63
73
  * @param {string} llmSocketUrl
74
+ * @param {string} datachannelToken
75
+ * @param {string} sessionId - Connection identifier
64
76
  * @returns {Promise<void>}
65
77
  */
66
- private register = (llmSocketUrl: string): Promise<void> =>
67
- this.request({
78
+ private register = async (
79
+ llmSocketUrl: string,
80
+ datachannelToken?: string,
81
+ sessionId: string = LLM_DEFAULT_SESSION
82
+ ): Promise<void> => {
83
+ const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
84
+
85
+ return this.request({
68
86
  method: 'POST',
69
87
  url: llmSocketUrl,
70
88
  body: {deviceUrl: this.webex.internal.device.url},
89
+ headers:
90
+ isDataChannelTokenEnabled && datachannelToken
91
+ ? {'Data-Channel-Auth-Token': datachannelToken}
92
+ : {},
71
93
  })
72
94
  .then((res: {body: {webSocketUrl: string; binding: string}}) => {
73
- this.webSocketUrl = res.body.webSocketUrl;
74
- this.binding = res.body.binding;
95
+ // Get or create connection data
96
+ const sessionData = this.connections.get(sessionId) || {};
97
+ sessionData.webSocketUrl = res.body.webSocketUrl;
98
+ sessionData.binding = res.body.binding;
99
+ this.connections.set(sessionId, sessionData);
75
100
  })
76
101
  .catch((error: any) => {
77
- this.logger.error(`Error connecting to websocket: ${error}`);
102
+ this.logger.error(`Error connecting to websocket for ${sessionId}: ${error}`);
78
103
  throw error;
79
104
  });
105
+ };
80
106
 
81
107
  /**
82
108
  * Register and connect to the websocket
83
109
  * @param {string} locusUrl
84
110
  * @param {string} datachannelUrl
111
+ * @param {string} datachannelToken
112
+ * @param {string} sessionId - Connection identifier
85
113
  * @returns {Promise<void>}
86
114
  */
87
- public registerAndConnect = (locusUrl: string, datachannelUrl: string): Promise<void> =>
88
- this.register(datachannelUrl).then(() => {
115
+ public registerAndConnect = (
116
+ locusUrl: string,
117
+ datachannelUrl: string,
118
+ datachannelToken?: string,
119
+ sessionId: string = LLM_DEFAULT_SESSION
120
+ ): Promise<void> =>
121
+ this.register(datachannelUrl, datachannelToken, sessionId).then(() => {
89
122
  if (!locusUrl || !datachannelUrl) return undefined;
90
- this.locusUrl = locusUrl;
91
- this.datachannelUrl = datachannelUrl;
92
- this.connect(this.webSocketUrl);
123
+
124
+ // Get or create connection data
125
+ const sessionData = this.connections.get(sessionId) || {};
126
+ sessionData.locusUrl = locusUrl;
127
+ sessionData.datachannelUrl = datachannelUrl;
128
+ sessionData.datachannelToken = datachannelToken;
129
+ this.connections.set(sessionId, sessionData);
130
+
131
+ return this.connect(sessionData.webSocketUrl, sessionId);
93
132
  });
94
133
 
95
134
  /**
96
135
  * Tells if LLM socket is connected
136
+ * @param {string} sessionId - Connection identifier
97
137
  * @returns {boolean} connected
98
138
  */
99
- public isConnected = (): boolean => this.connected;
139
+ public isConnected = (sessionId = LLM_DEFAULT_SESSION): boolean => {
140
+ const socket = this.getSocket(sessionId);
141
+
142
+ return socket ? socket.connected : false;
143
+ };
100
144
 
101
145
  /**
102
146
  * Tells if LLM socket is binding
147
+ * @param {string} sessionId - Connection identifier
103
148
  * @returns {string} binding
104
149
  */
105
- public getBinding = (): string => this.binding;
150
+ public getBinding = (sessionId = LLM_DEFAULT_SESSION): string => {
151
+ const sessionData = this.connections.get(sessionId);
152
+
153
+ return sessionData?.binding;
154
+ };
106
155
 
107
156
  /**
108
157
  * Get Locus URL for the connection
158
+ * @param {string} sessionId - Connection identifier
109
159
  * @returns {string} locus Url
110
160
  */
111
- public getLocusUrl = (): string => this.locusUrl;
161
+ public getLocusUrl = (sessionId = LLM_DEFAULT_SESSION): string => {
162
+ const sessionData = this.connections.get(sessionId);
163
+
164
+ return sessionData?.locusUrl;
165
+ };
112
166
 
113
167
  /**
114
168
  * Get data channel URL for the connection
169
+ * @param {string} sessionId - Connection identifier
115
170
  * @returns {string} data channel Url
116
171
  */
117
- public getDatachannelUrl = (): string => this.datachannelUrl;
172
+ public getDatachannelUrl = (sessionId = LLM_DEFAULT_SESSION): string => {
173
+ const sessionData = this.connections.get(sessionId);
174
+
175
+ return sessionData?.datachannelUrl;
176
+ };
177
+
178
+ /**
179
+ * Get data channel token for the connection
180
+ * @param {DataChannelTokenType} dataChannelTokenType
181
+ * @returns {string} data channel token
182
+ */
183
+ public getDatachannelToken = (dataChannelTokenType: DataChannelTokenType): string => {
184
+ return this.datachannelTokens[dataChannelTokenType];
185
+ };
186
+
187
+ /**
188
+ * Set data channel token for the connection
189
+ * @param {string} datachannelToken - data channel token
190
+ * @param {DataChannelTokenType} dataChannelTokenType
191
+ * @returns {void}
192
+ */
193
+ public setDatachannelToken = (
194
+ datachannelToken: string,
195
+ dataChannelTokenType: DataChannelTokenType
196
+ ): void => {
197
+ this.datachannelTokens[dataChannelTokenType] = datachannelToken;
198
+ };
199
+
200
+ /**
201
+ * Set the handler used to refresh the DataChannel token
202
+ *
203
+ * @param {function} handler - Function that returns a refreshed token
204
+ * @returns {void}
205
+ */
206
+ public setRefreshHandler(
207
+ handler: () => Promise<{
208
+ body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
209
+ }>
210
+ ) {
211
+ this.refreshHandler = handler;
212
+ }
213
+
214
+ /**
215
+ * Refresh the data channel token using the injected handler.
216
+ * Logs a descriptive error if the handler is missing or fails.
217
+ *
218
+ * @returns {Promise<string>} The refreshed token.
219
+ */
220
+ public async refreshDataChannelToken() {
221
+ if (!this.refreshHandler) {
222
+ const error = new Error('LLM refreshHandler is not set');
223
+ this.logger.error(`Error refreshing DataChannel token: ${error.message}`);
224
+ throw error;
225
+ }
226
+
227
+ try {
228
+ const res = await this.refreshHandler();
229
+
230
+ return res;
231
+ } catch (error: any) {
232
+ this.logger.error(`Error refreshing DataChannel token: ${error}`);
233
+ throw error;
234
+ }
235
+ }
118
236
 
119
237
  /**
120
238
  * Disconnects websocket connection
121
239
  * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
240
+ * @param {string} sessionId - Connection identifier
241
+ * @returns {Promise<void>}
242
+ */
243
+ public disconnectLLM = (
244
+ options: {code: number; reason: string},
245
+ sessionId: string = LLM_DEFAULT_SESSION
246
+ ): Promise<void> =>
247
+ this.disconnect(options, sessionId).then(() => {
248
+ // Clean up sessions data
249
+ this.connections.delete(sessionId);
250
+ });
251
+
252
+ /**
253
+ * Disconnects all LLM websocket connections
254
+ * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
122
255
  * @returns {Promise<void>}
123
256
  */
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;
257
+ public disconnectAllLLM = (options?: {code: number; reason: string}): Promise<void> =>
258
+ this.disconnectAll(options).then(() => {
259
+ // Clean up all connection data
260
+ this.connections.clear();
130
261
  });
262
+
263
+ /**
264
+ * Get all active LLM connections
265
+ * @returns {Map} Map of sessionId to session data
266
+ */
267
+ public getAllConnections = (): Map<
268
+ string,
269
+ {
270
+ webSocketUrl?: string;
271
+ binding?: string;
272
+ locusUrl?: string;
273
+ datachannelUrl?: string;
274
+ datachannelToken?: string;
275
+ }
276
+ > => new Map(this.connections);
277
+
278
+ /**
279
+ * Returns true if data channel token is enabled, false otherwise
280
+ * @returns {Promise<boolean>} resolves with true if data channel token is enabled
281
+ */
282
+ public isDataChannelTokenEnabled(): Promise<boolean> {
283
+ // @ts-ignore
284
+ return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);
285
+ }
131
286
  }
package/src/llm.types.ts CHANGED
@@ -1,9 +1,32 @@
1
1
  interface ILLMChannel {
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>;
2
+ registerAndConnect: (
3
+ locusUrl: string,
4
+ datachannelUrl: string,
5
+ datachannelToken?: string,
6
+ sessionId?: string
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
+ >;
7
24
  }
25
+
26
+ export enum DataChannelTokenType {
27
+ Default = 'default',
28
+ PracticeSession = 'practiceSession',
29
+ }
30
+
8
31
  // eslint-disable-next-line import/prefer-default-export
9
32
  export type {ILLMChannel};
@@ -19,9 +19,15 @@ 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
28
  llmService.connect = sinon.stub().callsFake(() => {
24
- llmService.connected = true;
29
+ // Simulate a successful connection by stubbing getSocket to return connected: true
30
+ llmService.getSocket = sinon.stub().returns({connected: true});
25
31
  });
26
32
  llmService.disconnect = sinon.stub().resolves(true);
27
33
  llmService.request = sinon.stub().resolves({
@@ -56,10 +62,51 @@ describe('plugin-llm', () => {
56
62
  await llmService.registerAndConnect();
57
63
  assert.equal(llmService.isConnected(), false);
58
64
  });
65
+
66
+ it('registers connection with token', async () => {
67
+ llmService.register = sinon.stub().resolves({
68
+ body: {
69
+ binding: 'binding',
70
+ webSocketUrl: 'url',
71
+ },
72
+ });
73
+
74
+ assert.equal(llmService.isConnected(), false);
75
+
76
+ await llmService.registerAndConnect(locusUrl, datachannelUrl, 'abc123');
77
+
78
+ sinon.assert.calledOnceWithExactly(
79
+ llmService.register,
80
+ datachannelUrl,
81
+ 'abc123',
82
+ 'llm-default-session'
83
+ );
84
+
85
+ assert.equal(llmService.isConnected(), true);
86
+ });
59
87
  });
60
88
 
61
89
  describe('#register', () => {
62
- it('registers connection', async () => {
90
+ beforeEach(() => {
91
+ llmService.isDataChannelTokenEnabled = sinon.stub();
92
+ });
93
+
94
+ it('registers connection with token header', async () => {
95
+ llmService.isDataChannelTokenEnabled.resolves(true);
96
+ await llmService.register(datachannelUrl, 'abc123');
97
+
98
+ sinon.assert.calledOnceWithExactly(
99
+ llmService.request,
100
+ sinon.match({
101
+ method: 'POST',
102
+ url: `${datachannelUrl}`,
103
+ body: {deviceUrl: webex.internal.device.url},
104
+ headers: {'Data-Channel-Auth-Token': 'abc123'},
105
+ })
106
+ );
107
+ });
108
+
109
+ it('registers connection without token header when none provided', async () => {
63
110
  await llmService.register(datachannelUrl);
64
111
 
65
112
  sinon.assert.calledOnceWithExactly(
@@ -68,13 +115,28 @@ describe('plugin-llm', () => {
68
115
  method: 'POST',
69
116
  url: `${datachannelUrl}`,
70
117
  body: {deviceUrl: webex.internal.device.url},
118
+ headers: {},
71
119
  })
72
120
  );
121
+ });
73
122
 
74
- assert.equal(llmService.getBinding(), 'binding');
123
+ it('registers connection without token header when toggle disabled', async () => {
124
+ llmService.isDataChannelTokenEnabled.resolves(false);
125
+
126
+ await llmService.register(datachannelUrl,'abc123');
127
+ sinon.assert.calledOnceWithExactly(
128
+ llmService.request,
129
+ sinon.match({
130
+ method: 'POST',
131
+ url: `${datachannelUrl}`,
132
+ body: {deviceUrl: webex.internal.device.url},
133
+ headers: {},
134
+ })
135
+ );
75
136
  });
76
137
  });
77
138
 
139
+
78
140
  describe('#getLocusUrl', () => {
79
141
  it('gets LocusUrl', async () => {
80
142
  llmService.register = sinon.stub().resolves({
@@ -149,5 +211,141 @@ describe('plugin-llm', () => {
149
211
  await expect(instance.disconnectLLM({})).rejects.toThrow('Disconnect failed');
150
212
  });
151
213
  });
214
+
215
+ describe('#setRefreshHandler', () => {
216
+ it('stores the provided handler', () => {
217
+ const handler = sinon.stub().resolves({ body: { datachannelToken: 'newToken' } });
218
+ llmService.setRefreshHandler(handler);
219
+
220
+ // @ts-ignore
221
+ assert.equal(llmService.refreshHandler, handler);
222
+ });
223
+ });
224
+
225
+ describe('#isDataChannelTokenEnabled', () => {
226
+ it('works correctly', async () => {
227
+ webex.internal.feature.getFeature.returns(true);
228
+
229
+ const result = await llmService.isDataChannelTokenEnabled();
230
+
231
+ sinon.assert.calledOnceWithExactly(
232
+ webex.internal.feature.getFeature,
233
+ 'developer',
234
+ 'data-channel-with-jwt-token'
235
+ );
236
+
237
+ assert.equal(result, true);
238
+ });
239
+ });
240
+
241
+ describe('#refreshDataChannelToken', () => {
242
+ it('throws if no handler is set', async () => {
243
+ try {
244
+ await llmService.refreshDataChannelToken();
245
+ assert.fail('Should have thrown');
246
+ } catch (err) {
247
+ assert.match(err.message, 'LLM refreshHandler is not set');
248
+ }
249
+ });
250
+
251
+ it('returns token when handler resolves', async () => {
252
+ const mockToken = { body: { datachannelToken: 'newToken' ,isPracticeSession: false} }
253
+ const handler = sinon.stub().resolves(mockToken);
254
+ llmService.setRefreshHandler(handler);
255
+
256
+ const token = await llmService.refreshDataChannelToken();
257
+ assert.equal(token, mockToken);
258
+ sinon.assert.calledOnce(handler);
259
+ });
260
+
261
+ it('logs and rethrows when handler rejects', async () => {
262
+ const handler = sinon.stub().rejects(new Error('throw error'));
263
+
264
+ const loggerSpy = llmService.logger.error;
265
+
266
+ llmService.setRefreshHandler(handler);
267
+
268
+ try {
269
+ await llmService.refreshDataChannelToken();
270
+ assert.fail('Should have thrown');
271
+ } catch (err) {
272
+ assert.match(err.message, /throw error/);
273
+ }
274
+
275
+ sinon.assert.calledOnce(loggerSpy);
276
+ sinon.assert.calledWithMatch(
277
+ loggerSpy,
278
+ sinon.match("Error refreshing DataChannel token: Error: throw error")
279
+ );
280
+ });
281
+ });
282
+
283
+ describe('#getDatachannelToken / #setDatachannelToken', () => {
284
+ it('sets and gets datachannel token', () => {
285
+ llmService.setDatachannelToken('abc123','default');
286
+ assert.equal(llmService.getDatachannelToken('default'), 'abc123');
287
+ llmService.setDatachannelToken('123abc','practiceSession');
288
+ assert.equal(llmService.getDatachannelToken('practiceSession'), '123abc');
289
+ });
290
+ });
291
+
292
+ describe('multi-connection logic', () => {
293
+ const locusUrl2 = 'locusUrl2';
294
+ const datachannelUrl2 = 'datachannelUrl2';
295
+
296
+ beforeEach(() => {
297
+ const sockets = new Map();
298
+
299
+ llmService.connect = sinon.stub().callsFake((url, sessionId) => {
300
+ sockets.set(sessionId, {connected: true});
301
+ llmService.getSocket = sinon.stub().callsFake((sid) => sockets.get(sid));
302
+ });
303
+ });
304
+
305
+ it('tracks multiple sessions independently', async () => {
306
+ await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
307
+ await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
308
+
309
+ assert.equal(llmService.isConnected('s1'), true);
310
+ assert.equal(llmService.isConnected('s2'), true);
311
+ assert.equal(llmService.getLocusUrl('s1'), locusUrl);
312
+ assert.equal(llmService.getLocusUrl('s2'), locusUrl2);
313
+ assert.equal(llmService.getDatachannelUrl('s1'), datachannelUrl);
314
+ assert.equal(llmService.getDatachannelUrl('s2'), datachannelUrl2);
315
+
316
+ const all = llmService.getAllConnections();
317
+ assert.equal(all.size, 2);
318
+ assert.equal(all.has('s1'), true);
319
+ assert.equal(all.has('s2'), true);
320
+ });
321
+
322
+ it('disconnectLLM clears only the targeted session', async () => {
323
+ llmService.disconnect = sinon.stub().resolves(true);
324
+
325
+ await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
326
+ await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
327
+
328
+ const options = {code: 1000, reason: 'test'};
329
+ await llmService.disconnectLLM(options, 's1');
330
+
331
+ sinon.assert.calledOnceWithExactly(llmService.disconnect, options, 's1');
332
+
333
+ const all = llmService.getAllConnections();
334
+ assert.equal(all.has('s1'), false);
335
+ assert.equal(all.has('s2'), true);
336
+ });
337
+
338
+ it('disconnectAllLLM clears all sessions', async () => {
339
+ llmService.disconnectAll = sinon.stub().resolves(true);
340
+
341
+ await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
342
+ await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
343
+
344
+ await llmService.disconnectAllLLM({code: 1000, reason: 'all'});
345
+
346
+ sinon.assert.calledOnce(llmService.disconnectAll);
347
+ assert.equal(llmService.getAllConnections().size, 0);
348
+ });
349
+ });
152
350
  });
153
351
  });