@webex/internal-plugin-llm 3.12.0-next.4 → 3.12.0-next.41

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
@@ -91,7 +91,7 @@ webex.internal.llm.setRefreshHandler(async () => {
91
91
  datachannelTokenType: 'llm-default-session',
92
92
  },
93
93
  };
94
- });
94
+ }, 'llm-default-session');
95
95
 
96
96
  // Optional: manually trigger refresh (if needed by your flow)
97
97
  await webex.internal.llm.refreshDataChannelToken();
@@ -106,7 +106,7 @@ webex.internal.llm.getDatachannelUrl('session-a');
106
106
  webex.internal.llm.getAllConnections();
107
107
 
108
108
  // Disconnect one session
109
- await webex.internal.llm.disconnectLLM({code: 1000, reason: 'done'}, 'session-a');
109
+ await webex.internal.llm.disconnectLLM({code: 1000, reason: 'done'}, 'session-a', 'meeting-id');
110
110
 
111
111
  // Disconnect all sessions
112
112
  await webex.internal.llm.disconnectAllLLM({code: 1000, reason: 'shutdown'});
package/dist/constants.js CHANGED
@@ -4,10 +4,11 @@ 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.SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = exports.LLM_DEFAULT_SESSION = exports.LLM = exports.DATA_CHNANEL_TYPE = exports.DATA_CHANNEL_WITH_JWT_TOKEN = exports.AWARE_DATA_CHANNEL = void 0;
7
+ exports.SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = exports.LLM_PRACTICE_SESSION = exports.LLM_DEFAULT_SESSION = exports.LLM = exports.DATA_CHNANEL_TYPE = exports.DATA_CHANNEL_WITH_JWT_TOKEN = exports.AWARE_DATA_CHANNEL = void 0;
8
8
  // eslint-disable-next-line import/prefer-default-export
9
9
  var LLM = exports.LLM = 'llm';
10
10
  var LLM_DEFAULT_SESSION = exports.LLM_DEFAULT_SESSION = 'llm-default-session';
11
+ var LLM_PRACTICE_SESSION = exports.LLM_PRACTICE_SESSION = 'llm-practice-session';
11
12
  var DATA_CHANNEL_WITH_JWT_TOKEN = exports.DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';
12
13
  var SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = exports.SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';
13
14
  var DATA_CHNANEL_TYPE = exports.DATA_CHNANEL_TYPE = {
@@ -1 +1 @@
1
- {"version":3,"names":["LLM","exports","LLM_DEFAULT_SESSION","DATA_CHANNEL_WITH_JWT_TOKEN","SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM","DATA_CHNANEL_TYPE","TRANSCRIPTION","AWARE_DATA_CHANNEL"],"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\nexport const SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';\n\nexport const DATA_CHNANEL_TYPE = {\n TRANSCRIPTION: 'transcription',\n};\n\nexport const AWARE_DATA_CHANNEL = [DATA_CHNANEL_TYPE.TRANSCRIPTION];\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;AAEjE,IAAMC,oCAAoC,GAAAH,OAAA,CAAAG,oCAAA,GAAG,8BAA8B;AAE3E,IAAMC,iBAAiB,GAAAJ,OAAA,CAAAI,iBAAA,GAAG;EAC/BC,aAAa,EAAE;AACjB,CAAC;AAEM,IAAMC,kBAAkB,GAAAN,OAAA,CAAAM,kBAAA,GAAG,CAACF,iBAAiB,CAACC,aAAa,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["LLM","exports","LLM_DEFAULT_SESSION","LLM_PRACTICE_SESSION","DATA_CHANNEL_WITH_JWT_TOKEN","SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM","DATA_CHNANEL_TYPE","TRANSCRIPTION","AWARE_DATA_CHANNEL"],"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';\nexport const LLM_PRACTICE_SESSION = 'llm-practice-session';\n\nexport const DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';\n\nexport const SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';\n\nexport const DATA_CHNANEL_TYPE = {\n TRANSCRIPTION: 'transcription',\n};\n\nexport const AWARE_DATA_CHANNEL = [DATA_CHNANEL_TYPE.TRANSCRIPTION];\n"],"mappings":";;;;;;;AAAA;AACO,IAAMA,GAAG,GAAAC,OAAA,CAAAD,GAAA,GAAG,KAAK;AAEjB,IAAME,mBAAmB,GAAAD,OAAA,CAAAC,mBAAA,GAAG,qBAAqB;AACjD,IAAMC,oBAAoB,GAAAF,OAAA,CAAAE,oBAAA,GAAG,sBAAsB;AAEnD,IAAMC,2BAA2B,GAAAH,OAAA,CAAAG,2BAAA,GAAG,6BAA6B;AAEjE,IAAMC,oCAAoC,GAAAJ,OAAA,CAAAI,oCAAA,GAAG,8BAA8B;AAE3E,IAAMC,iBAAiB,GAAAL,OAAA,CAAAK,iBAAA,GAAG;EAC/BC,aAAa,EAAE;AACjB,CAAC;AAEM,IAAMC,kBAAkB,GAAAP,OAAA,CAAAO,kBAAA,GAAG,CAACF,iBAAiB,CAACC,aAAa,CAAC","ignoreList":[]}
package/dist/index.js CHANGED
@@ -13,6 +13,18 @@ _Object$defineProperty(exports, "DataChannelTokenType", {
13
13
  return _llm2.DataChannelTokenType;
14
14
  }
15
15
  });
16
+ _Object$defineProperty(exports, "LLM_DEFAULT_SESSION", {
17
+ enumerable: true,
18
+ get: function get() {
19
+ return _constants.LLM_DEFAULT_SESSION;
20
+ }
21
+ });
22
+ _Object$defineProperty(exports, "LLM_PRACTICE_SESSION", {
23
+ enumerable: true,
24
+ get: function get() {
25
+ return _constants.LLM_PRACTICE_SESSION;
26
+ }
27
+ });
16
28
  _Object$defineProperty(exports, "default", {
17
29
  enumerable: true,
18
30
  get: function get() {
@@ -22,6 +34,7 @@ _Object$defineProperty(exports, "default", {
22
34
  var WebexCore = _interopRequireWildcard(require("@webex/webex-core"));
23
35
  var _llm = _interopRequireWildcard(require("./llm"));
24
36
  var _llm2 = require("./llm.types");
37
+ var _constants = require("./constants");
25
38
  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); }
26
39
  WebexCore.registerInternalPlugin('llm', _llm.default, {
27
40
  config: _llm.config
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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":[]}
1
+ {"version":3,"names":["WebexCore","_interopRequireWildcard","require","_llm","_llm2","_constants","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\nexport type {RegisterAndConnectTiming} from './llm.types';\n\nWebexCore.registerInternalPlugin('llm', LLMChannel, {\n config,\n});\n\nexport {DataChannelTokenType};\nexport {LLM_DEFAULT_SESSION, LLM_PRACTICE_SESSION} from './constants';\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;AASA,IAAAG,UAAA,GAAAH,OAAA;AAAsE,SAAAD,wBAAAK,CAAA,EAAAC,CAAA,6BAAAC,QAAA,MAAAC,CAAA,OAAAD,QAAA,IAAAE,CAAA,OAAAF,QAAA,YAAAP,uBAAA,YAAAA,wBAAAK,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;AALtEP,SAAS,CAAC0B,sBAAsB,CAAC,KAAK,EAAEC,YAAU,EAAE;EAClDC,MAAM,EAANA;AACF,CAAC,CAAC","ignoreList":[]}
package/dist/llm.js CHANGED
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  var _Reflect$construct = require("@babel/runtime-corejs2/core-js/reflect/construct");
4
+ var _Array$from = require("@babel/runtime-corejs2/core-js/array/from");
5
+ var _Symbol = require("@babel/runtime-corejs2/core-js/symbol");
6
+ var _Symbol$iterator = require("@babel/runtime-corejs2/core-js/symbol/iterator");
7
+ var _Array$isArray = require("@babel/runtime-corejs2/core-js/array/is-array");
4
8
  var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
5
9
  var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
6
10
  _Object$defineProperty(exports, "__esModule", {
@@ -9,6 +13,8 @@ _Object$defineProperty(exports, "__esModule", {
9
13
  exports.default = exports.config = void 0;
10
14
  var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
11
15
  var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map"));
16
+ var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
17
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/slicedToArray"));
12
18
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));
13
19
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
14
20
  var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
@@ -19,6 +25,9 @@ var _defineProperty3 = _interopRequireDefault(require("@babel/runtime-corejs2/he
19
25
  var _internalPluginMercury = _interopRequireDefault(require("@webex/internal-plugin-mercury"));
20
26
  var _constants = require("./constants");
21
27
  var _llm = require("./llm.types");
28
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof _Symbol && r[_Symbol$iterator] || r["@@iterator"]; if (!t) { if (_Array$isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
29
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? _Array$from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
30
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
22
31
  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)); }
23
32
  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
24
33
  var config = exports.config = {
@@ -67,12 +76,14 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
67
76
  (0, _defineProperty3.default)(_this, "defaultSessionId", _constants.LLM_DEFAULT_SESSION);
68
77
  /**
69
78
  * Map to store connection-specific data for multiple LLM connections
79
+ * Key: sessionId
70
80
  * @private
71
81
  * @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}
72
82
  */
73
83
  (0, _defineProperty3.default)(_this, "connections", new _map.default());
84
+ // Session-keyed token cache is intentionally decoupled from connection state.
85
+ // Disconnecting a socket session must not implicitly wipe token cache.
74
86
  (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);
76
87
  /**
77
88
  * Register to the websocket
78
89
  * @param {string} llmSocketUrl
@@ -132,8 +143,19 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
132
143
  */
133
144
  (0, _defineProperty3.default)(_this, "registerAndConnect", function (locusUrl, datachannelUrl, datachannelToken) {
134
145
  var sessionId = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _constants.LLM_DEFAULT_SESSION;
146
+ var registerStart = performance.now();
147
+
148
+ // Pre-populate locusUrl and datachannelUrl before register() fires the
149
+ // HTTP POST, so that any token refresh triggered during registration can
150
+ // be routed via connections without falling back to a locusInfo URL scan.
151
+ if (locusUrl && datachannelUrl) {
152
+ var sessionData = _this.connections.get(sessionId) || {};
153
+ sessionData.locusUrl = locusUrl;
154
+ sessionData.datachannelUrl = datachannelUrl;
155
+ _this.connections.set(sessionId, sessionData);
156
+ }
135
157
  return _this.register(datachannelUrl, datachannelToken, sessionId).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
136
- var sessionData, isDataChannelTokenEnabled, connectUrl;
158
+ var clientLLMDatachannelResponseTime, sessionData, error, isDataChannelTokenEnabled, connectUrl, connectStart, clientLLMWebSocketConnectTime, _t;
137
159
  return _regenerator.default.wrap(function (_context2) {
138
160
  while (1) switch (_context2.prev = _context2.next) {
139
161
  case 0:
@@ -143,23 +165,55 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
143
165
  }
144
166
  return _context2.abrupt("return", undefined);
145
167
  case 1:
146
- // Get or create connection data
168
+ clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart); // locusUrl and datachannelUrl were pre-populated before register(); here
169
+ // we only need to read the existing session data to get webSocketUrl/binding
170
+ // that register() filled in.
147
171
  sessionData = _this.connections.get(sessionId) || {};
148
- sessionData.locusUrl = locusUrl;
149
- sessionData.datachannelUrl = datachannelUrl;
150
- sessionData.datachannelToken = datachannelToken;
151
- _this.connections.set(sessionId, sessionData);
152
- _context2.next = 2;
153
- return _this.isDataChannelTokenEnabled();
172
+ if (sessionData.webSocketUrl) {
173
+ _context2.next = 2;
174
+ break;
175
+ }
176
+ // register() succeeded but the response carried no websocket URL. Attach the measured
177
+ // datachannel time so callers don't misreport a registration that completed as time 0.
178
+ error = new Error("LLM registration for ".concat(sessionId, " returned no websocket URL"));
179
+ error.timing = {
180
+ clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime
181
+ };
182
+ throw error;
154
183
  case 2:
184
+ _context2.next = 3;
185
+ return _this.isDataChannelTokenEnabled();
186
+ case 3:
155
187
  isDataChannelTokenEnabled = _context2.sent;
156
188
  connectUrl = isDataChannelTokenEnabled ? LLMChannel.buildUrlWithAwareSubchannels(sessionData.webSocketUrl, _constants.AWARE_DATA_CHANNEL) : sessionData.webSocketUrl;
157
- return _context2.abrupt("return", _this.connect(connectUrl, sessionId));
158
- case 3:
189
+ connectStart = performance.now();
190
+ _context2.prev = 4;
191
+ _context2.next = 5;
192
+ return _this.connect(connectUrl, sessionId);
193
+ case 5:
194
+ _context2.next = 7;
195
+ break;
196
+ case 6:
197
+ _context2.prev = 6;
198
+ _t = _context2["catch"](4);
199
+ // register() succeeded; only connect() failed. Attach the measured datachannel time so
200
+ // callers don't misreport a websocket failure as a registration that never completed.
201
+ // @ts-ignore
202
+ _t.timing = {
203
+ clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime
204
+ };
205
+ throw _t;
206
+ case 7:
207
+ clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);
208
+ return _context2.abrupt("return", {
209
+ clientLLMDatachannelResponseTime: clientLLMDatachannelResponseTime,
210
+ clientLLMWebSocketConnectTime: clientLLMWebSocketConnectTime
211
+ });
212
+ case 8:
159
213
  case "end":
160
214
  return _context2.stop();
161
215
  }
162
- }, _callee2);
216
+ }, _callee2, null, [[4, 6]]);
163
217
  })));
164
218
  });
165
219
  /**
@@ -202,36 +256,159 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
202
256
  var sessionData = _this.connections.get(sessionId);
203
257
  return sessionData === null || sessionData === void 0 ? void 0 : sessionData.datachannelUrl;
204
258
  });
259
+ /**
260
+ * Get WebSocket URL for the connection
261
+ * @param {string} sessionId - Connection identifier
262
+ * @returns {string | undefined} WebSocket URL
263
+ */
264
+ (0, _defineProperty3.default)(_this, "getWebSocketUrl", function () {
265
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _constants.LLM_DEFAULT_SESSION;
266
+ var sessionData = _this.connections.get(sessionId);
267
+ return sessionData === null || sessionData === void 0 ? void 0 : sessionData.webSocketUrl;
268
+ });
269
+ /**
270
+ * Set the owner meeting ID for a given LLM session. Used by the meetings
271
+ * plugin to tag which Meeting instance currently owns the (default) LLM
272
+ * connection so that other Meeting instances can avoid disconnecting or
273
+ * re-initializing a connection they do not own.
274
+ *
275
+ * Does NOT create a connections entry if one does not already exist — this
276
+ * method is a no-op when there is no active session data. Callers should
277
+ * invoke it after a successful `registerAndConnect` or during an explicit
278
+ * ownership handoff.
279
+ *
280
+ * @param {string | undefined} ownerMeetingId - Meeting ID (or undefined to clear)
281
+ * @param {string} sessionId - Connection identifier (defaults to default session)
282
+ * @returns {void}
283
+ */
284
+ (0, _defineProperty3.default)(_this, "setOwnerMeetingId", function (ownerMeetingId) {
285
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.LLM_DEFAULT_SESSION;
286
+ var sessionData = _this.connections.get(sessionId);
287
+ if (!sessionData) {
288
+ return;
289
+ }
290
+ sessionData.ownerMeetingId = ownerMeetingId;
291
+ _this.connections.set(sessionId, sessionData);
292
+ });
293
+ /**
294
+ * Get the owner meeting ID currently associated with an LLM session.
295
+ * Returns undefined when no owner has been assigned (e.g. before the
296
+ * first successful `registerAndConnect`, or after `disconnectLLM`).
297
+ *
298
+ * @param {string} sessionId - Connection identifier (defaults to default session)
299
+ * @returns {string | undefined} ownerMeetingId
300
+ */
301
+ (0, _defineProperty3.default)(_this, "getOwnerMeetingId", function () {
302
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _constants.LLM_DEFAULT_SESSION;
303
+ var sessionData = _this.connections.get(sessionId);
304
+ return sessionData === null || sessionData === void 0 ? void 0 : sessionData.ownerMeetingId;
305
+ });
306
+ /**
307
+ * Resolve ownership information for an LLM session.
308
+ *
309
+ * Rules:
310
+ * - no current owner => caller may proceed
311
+ * - caller has no identity to assert => treat as owner
312
+ * - otherwise caller must match current owner
313
+ *
314
+ * @param {string | undefined} ownerMeetingId - Candidate owner to evaluate
315
+ * @param {string} sessionId - Connection identifier (defaults to default session)
316
+ * @returns {{currentOwner: (string|undefined), isOwner: boolean}}
317
+ */
318
+ (0, _defineProperty3.default)(_this, "resolveSessionOwnership", function (ownerMeetingId) {
319
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.LLM_DEFAULT_SESSION;
320
+ var currentOwner = _this.getOwnerMeetingId(sessionId);
321
+ var isOwner = !currentOwner || !ownerMeetingId || currentOwner === ownerMeetingId;
322
+ return {
323
+ currentOwner: currentOwner,
324
+ isOwner: isOwner
325
+ };
326
+ });
205
327
  /**
206
328
  * Get data channel token for the connection
207
- * @param {DataChannelTokenType} dataChannelTokenType
208
- * @returns {string} data channel token
329
+ * @param {DataChannelTokenType|string} tokenKey
330
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting read ownership
331
+ * @returns {string | undefined} data channel token
209
332
  */
210
- (0, _defineProperty3.default)(_this, "getDatachannelToken", function () {
211
- var dataChannelTokenType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _llm.DataChannelTokenType.Default;
212
- return _this.datachannelTokens[dataChannelTokenType];
333
+ (0, _defineProperty3.default)(_this, "getDatachannelToken", function (tokenKey, ownerMeetingId) {
334
+ var resolvedTokenKey = tokenKey !== null && tokenKey !== void 0 ? tokenKey : _llm.DataChannelTokenType.Default;
335
+ var _this$resolveSessionO = _this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey),
336
+ currentOwner = _this$resolveSessionO.currentOwner,
337
+ isOwner = _this$resolveSessionO.isOwner;
338
+ if (!isOwner) {
339
+ _this.logger.info("llm#getDatachannelToken --> skip read for session ".concat(resolvedTokenKey, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
340
+ return undefined;
341
+ }
342
+ return _this.datachannelTokens[resolvedTokenKey];
213
343
  });
214
344
  /**
215
345
  * Set data channel token for the connection
216
346
  * @param {string} datachannelToken - data channel token
217
- * @param {DataChannelTokenType} dataChannelTokenType
347
+ * @param {DataChannelTokenType|string} [tokenKey]
348
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting write ownership
349
+ * @returns {void}
350
+ */
351
+ (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken, tokenKey, ownerMeetingId) {
352
+ var resolvedTokenKey = tokenKey !== null && tokenKey !== void 0 ? tokenKey : _llm.DataChannelTokenType.Default;
353
+ var _this$resolveSessionO2 = _this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey),
354
+ currentOwner = _this$resolveSessionO2.currentOwner,
355
+ isOwner = _this$resolveSessionO2.isOwner;
356
+ if (!isOwner) {
357
+ _this.logger.info("llm#setDatachannelToken --> skip write for session ".concat(resolvedTokenKey, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
358
+ return;
359
+ }
360
+ _this.datachannelTokens[resolvedTokenKey] = datachannelToken;
361
+ });
362
+ /**
363
+ * Clears a single session's data channel token.
364
+ * @param {DataChannelTokenType|string} tokenKey
365
+ * @param {string} ownerMeetingId - Meeting id asserting delete ownership
218
366
  * @returns {void}
219
367
  */
220
- (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken) {
221
- var dataChannelTokenType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _llm.DataChannelTokenType.Default;
222
- _this.datachannelTokens[dataChannelTokenType] = datachannelToken;
368
+ (0, _defineProperty3.default)(_this, "clearDatachannelToken", function (tokenKey, ownerMeetingId) {
369
+ var resolvedTokenKey = tokenKey;
370
+ var _this$resolveSessionO3 = _this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey),
371
+ currentOwner = _this$resolveSessionO3.currentOwner,
372
+ isOwner = _this$resolveSessionO3.isOwner;
373
+ if (!isOwner) {
374
+ _this.logger.info("llm#clearDatachannelToken --> skip clear for session ".concat(resolvedTokenKey, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
375
+ return;
376
+ }
377
+ _this.datachannelTokens[resolvedTokenKey] = undefined;
378
+ delete _this.datachannelTokens[resolvedTokenKey];
223
379
  });
224
380
  /**
225
381
  * Disconnects websocket connection
226
382
  * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
227
383
  * @param {string} sessionId - Connection identifier
228
- * @returns {Promise<void>}
384
+ * @param {string} ownerMeetingId - Meeting id asserting disconnect ownership
385
+ * @returns {Promise<boolean>} True when disconnect was performed, false when skipped
229
386
  */
230
- (0, _defineProperty3.default)(_this, "disconnectLLM", function (options) {
231
- var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.LLM_DEFAULT_SESSION;
232
- return _this.disconnect(options, sessionId).then(function () {
387
+ (0, _defineProperty3.default)(_this, "disconnectLLM", function (options, sessionId, ownerMeetingId) {
388
+ var resolvedSessionId = sessionId !== null && sessionId !== void 0 ? sessionId : _constants.LLM_DEFAULT_SESSION;
389
+
390
+ // Backward-compat path: historically callers could omit ownerMeetingId
391
+ // (and sometimes sessionId). Reuse current owner when available so legacy
392
+ // calls remain best-effort without throwing at teardown time.
393
+ var resolvedOwnerMeetingId = ownerMeetingId || _this.getOwnerMeetingId(resolvedSessionId);
394
+ if (!ownerMeetingId) {
395
+ _this.logger.warn("llm#disconnectLLM --> ownerMeetingId is omitted for session ".concat(resolvedSessionId, "; using legacy compatibility path"));
396
+ }
397
+ var _this$resolveSessionO4 = _this.resolveSessionOwnership(resolvedOwnerMeetingId, resolvedSessionId),
398
+ currentOwner = _this$resolveSessionO4.currentOwner,
399
+ isOwner = _this$resolveSessionO4.isOwner;
400
+ if (!isOwner) {
401
+ _this.logger.info("llm#disconnectLLM --> skip disconnect for session ".concat(resolvedSessionId, "; owned by ").concat(currentOwner, ", candidate ").concat(resolvedOwnerMeetingId));
402
+ return _promise.default.resolve(false);
403
+ }
404
+ return _this.disconnect(options, resolvedSessionId).then(function () {
405
+ // Clear owner tag before cleanup to ensure it's not lingering
406
+ // if another meeting claimed it during disconnect
407
+ _this.setOwnerMeetingId(undefined, resolvedSessionId);
408
+
233
409
  // Clean up sessions data
234
- _this.connections.delete(sessionId);
410
+ _this.connections.delete(resolvedSessionId);
411
+ return true;
235
412
  });
236
413
  });
237
414
  /**
@@ -256,60 +433,83 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
256
433
  }
257
434
  (0, _inherits2.default)(LLMChannel, _ref);
258
435
  return (0, _createClass2.default)(LLMChannel, [{
259
- key: "resetDatachannelTokens",
436
+ key: "setRefreshHandler",
260
437
  value:
261
- /**
262
- * Resets all data‑channel tokens to their initial undefined values.
263
- * Used when leaving or disconnecting from a meeting.
264
- * @returns {void}
265
- */
266
- function resetDatachannelTokens() {
267
- this.datachannelTokens = (0, _defineProperty3.default)((0, _defineProperty3.default)({}, _llm.DataChannelTokenType.Default, undefined), _llm.DataChannelTokenType.PracticeSession, undefined);
268
- }
269
-
270
438
  /**
271
439
  * Set the handler used to refresh the DataChannel token
272
440
  *
273
441
  * @param {function} handler - Function that returns a refreshed token
442
+ * @param {string} [sessionId] - Connection identifier
443
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting refresh-handler ownership
274
444
  * @returns {void}
275
445
  */
276
- }, {
277
- key: "setRefreshHandler",
278
- value: function setRefreshHandler(handler) {
279
- this.refreshHandler = handler;
446
+ function setRefreshHandler(handler, sessionId, ownerMeetingId) {
447
+ var resolvedSessionId = sessionId !== null && sessionId !== void 0 ? sessionId : _constants.LLM_DEFAULT_SESSION;
448
+ var _this$resolveSessionO5 = this.resolveSessionOwnership(ownerMeetingId, resolvedSessionId),
449
+ currentOwner = _this$resolveSessionO5.currentOwner,
450
+ isOwner = _this$resolveSessionO5.isOwner;
451
+ if (!isOwner) {
452
+ this.logger.info("llm#setRefreshHandler --> skip write for session ".concat(resolvedSessionId, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
453
+ return;
454
+ }
455
+ var sessionData = this.connections.get(resolvedSessionId);
456
+ if (sessionData) {
457
+ sessionData.refreshHandler = handler;
458
+ if (ownerMeetingId) {
459
+ sessionData.ownerMeetingId = ownerMeetingId;
460
+ }
461
+ return;
462
+ }
463
+
464
+ // Intentionally allow a pre-connection session shape here.
465
+ // Some flows inject refreshHandler before register/connect so token refresh
466
+ // is already wired when the socket lifecycle starts. register()/
467
+ // registerAndConnect() will later fill webSocketUrl/binding/locusUrl/
468
+ // datachannelUrl into this same session entry.
469
+ this.connections.set(resolvedSessionId, {
470
+ refreshHandler: handler,
471
+ ownerMeetingId: ownerMeetingId
472
+ });
280
473
  }
281
474
 
282
475
  /**
283
476
  * Refresh the data channel token using the injected handler.
284
477
  * Logs a descriptive error if the handler is missing or fails.
285
- *
478
+ * @param {string} sessionId - Connection identifier (defaults to default session)
286
479
  * @returns {Promise<string>} The refreshed token.
287
480
  */
288
481
  }, {
289
482
  key: "refreshDataChannelToken",
290
483
  value: (function () {
291
484
  var _refreshDataChannelToken = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3() {
292
- var res, _t;
485
+ var _this$connections$get;
486
+ var sessionId,
487
+ refreshHandler,
488
+ res,
489
+ _args3 = arguments,
490
+ _t2;
293
491
  return _regenerator.default.wrap(function (_context3) {
294
492
  while (1) switch (_context3.prev = _context3.next) {
295
493
  case 0:
296
- if (this.refreshHandler) {
494
+ sessionId = _args3.length > 0 && _args3[0] !== undefined ? _args3[0] : _constants.LLM_DEFAULT_SESSION;
495
+ refreshHandler = (_this$connections$get = this.connections.get(sessionId)) === null || _this$connections$get === void 0 ? void 0 : _this$connections$get.refreshHandler;
496
+ if (refreshHandler) {
297
497
  _context3.next = 1;
298
498
  break;
299
499
  }
300
- this.logger.warn('llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh');
500
+ this.logger.warn("llm#refreshDataChannelToken --> LLM refreshHandler is not set for session ".concat(sessionId, ", skipping token refresh"));
301
501
  return _context3.abrupt("return", null);
302
502
  case 1:
303
503
  _context3.prev = 1;
304
504
  _context3.next = 2;
305
- return this.refreshHandler();
505
+ return refreshHandler();
306
506
  case 2:
307
507
  res = _context3.sent;
308
508
  return _context3.abrupt("return", res);
309
509
  case 3:
310
510
  _context3.prev = 3;
311
- _t = _context3["catch"](1);
312
- this.logger.warn("llm#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ".concat((_t === null || _t === void 0 ? void 0 : _t.message) || _t));
511
+ _t2 = _context3["catch"](1);
512
+ this.logger.warn("llm#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ".concat((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || _t2));
313
513
  return _context3.abrupt("return", null);
314
514
  case 4:
315
515
  case "end":
@@ -322,6 +522,74 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
322
522
  }
323
523
  return refreshDataChannelToken;
324
524
  }())
525
+ }, {
526
+ key: "getLocusUrlByDatachannelUrl",
527
+ value:
528
+ /**
529
+ * Look up the locusUrl associated with a datachannel request URL.
530
+ * Iterates all active LLM sessions and returns the locusUrl of the
531
+ * session whose stored datachannelUrl is a prefix of the given request URL.
532
+ *
533
+ * @param {string} requestUrl - The in-flight request URL to match
534
+ * @returns {string | undefined} The matching locusUrl, or undefined if not found
535
+ */
536
+ function getLocusUrlByDatachannelUrl(requestUrl) {
537
+ var _iterator = _createForOfIteratorHelper(this.connections),
538
+ _step;
539
+ try {
540
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
541
+ var _step$value = (0, _slicedToArray2.default)(_step.value, 2),
542
+ connection = _step$value[1];
543
+ if (connection.datachannelUrl && LLMChannel.matchesDatachannelRequestUrl(requestUrl, connection.datachannelUrl)) {
544
+ return connection.locusUrl;
545
+ }
546
+ }
547
+ } catch (err) {
548
+ _iterator.e(err);
549
+ } finally {
550
+ _iterator.f();
551
+ }
552
+ return undefined;
553
+ }
554
+
555
+ /**
556
+ * Look up the sessionId associated with a datachannel request URL.
557
+ * Iterates all active LLM sessions and returns the sessionId whose
558
+ * stored datachannelUrl is a prefix of the given request URL.
559
+ *
560
+ * @param {string} requestUrl - The in-flight request URL to match
561
+ * @returns {string | undefined} The matching sessionId, or undefined if not found
562
+ */
563
+ }, {
564
+ key: "getSessionIdByDatachannelUrl",
565
+ value: function getSessionIdByDatachannelUrl(requestUrl) {
566
+ var _iterator2 = _createForOfIteratorHelper(this.connections),
567
+ _step2;
568
+ try {
569
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
570
+ var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
571
+ sessionId = _step2$value[0],
572
+ connection = _step2$value[1];
573
+ if (connection.datachannelUrl && LLMChannel.matchesDatachannelRequestUrl(requestUrl, connection.datachannelUrl)) {
574
+ return sessionId;
575
+ }
576
+ }
577
+ } catch (err) {
578
+ _iterator2.e(err);
579
+ } finally {
580
+ _iterator2.f();
581
+ }
582
+ return undefined;
583
+ }
584
+
585
+ /**
586
+ * Matches a request URL to a stored datachannel registration URL.
587
+ * Host can differ (e.g. rewritten by hostmap interceptor), so we first
588
+ * try full URL prefix and then fall back to pathname prefix.
589
+ * @param {string} requestUrl
590
+ * @param {string} registrationUrl
591
+ * @returns {boolean}
592
+ */
325
593
  }, {
326
594
  key: "isDataChannelTokenEnabled",
327
595
  value:
@@ -341,6 +609,23 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
341
609
  * @param {string[]} subchannels - List of subchannels to declare as subscription-aware.
342
610
  * @returns {string} The final URL with updated query parameters.
343
611
  */
612
+ }], [{
613
+ key: "matchesDatachannelRequestUrl",
614
+ value: function matchesDatachannelRequestUrl(requestUrl, registrationUrl) {
615
+ if (!requestUrl || !registrationUrl) {
616
+ return false;
617
+ }
618
+ if (requestUrl.startsWith(registrationUrl)) {
619
+ return true;
620
+ }
621
+ try {
622
+ var request = new URL(requestUrl);
623
+ var registration = new URL(registrationUrl);
624
+ return request.pathname.startsWith(registration.pathname);
625
+ } catch (error) {
626
+ return false;
627
+ }
628
+ }
344
629
  }]);
345
630
  }(_internalPluginMercury.default);
346
631
  (0, _defineProperty3.default)(LLMChannel, "buildUrlWithAwareSubchannels", function (baseUrl, subchannels) {