@webex/internal-plugin-llm 3.12.0-next.19 → 3.12.0-next.20

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\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;AAOA,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,6 +143,15 @@ 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
+ // Pre-populate locusUrl and datachannelUrl before register() fires the
147
+ // HTTP POST, so that any token refresh triggered during registration can
148
+ // be routed via connections without falling back to a locusInfo URL scan.
149
+ if (locusUrl && datachannelUrl) {
150
+ var sessionData = _this.connections.get(sessionId) || {};
151
+ sessionData.locusUrl = locusUrl;
152
+ sessionData.datachannelUrl = datachannelUrl;
153
+ _this.connections.set(sessionId, sessionData);
154
+ }
135
155
  return _this.register(datachannelUrl, datachannelToken, sessionId).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
136
156
  var sessionData, isDataChannelTokenEnabled, connectUrl;
137
157
  return _regenerator.default.wrap(function (_context2) {
@@ -143,12 +163,10 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
143
163
  }
144
164
  return _context2.abrupt("return", undefined);
145
165
  case 1:
146
- // Get or create connection data
166
+ // locusUrl and datachannelUrl were pre-populated before register(); here
167
+ // we only need to read the existing session data to get webSocketUrl/binding
168
+ // that register() filled in.
147
169
  sessionData = _this.connections.get(sessionId) || {};
148
- sessionData.locusUrl = locusUrl;
149
- sessionData.datachannelUrl = datachannelUrl;
150
- sessionData.datachannelToken = datachannelToken;
151
- _this.connections.set(sessionId, sessionData);
152
170
  _context2.next = 2;
153
171
  return _this.isDataChannelTokenEnabled();
154
172
  case 2:
@@ -239,36 +257,112 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
239
257
  var sessionData = _this.connections.get(sessionId);
240
258
  return sessionData === null || sessionData === void 0 ? void 0 : sessionData.ownerMeetingId;
241
259
  });
260
+ /**
261
+ * Resolve ownership information for an LLM session.
262
+ *
263
+ * Rules:
264
+ * - no current owner => caller may proceed
265
+ * - caller has no identity to assert => treat as owner
266
+ * - otherwise caller must match current owner
267
+ *
268
+ * @param {string | undefined} ownerMeetingId - Candidate owner to evaluate
269
+ * @param {string} sessionId - Connection identifier (defaults to default session)
270
+ * @returns {{currentOwner: (string|undefined), isOwner: boolean}}
271
+ */
272
+ (0, _defineProperty3.default)(_this, "resolveSessionOwnership", function (ownerMeetingId) {
273
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.LLM_DEFAULT_SESSION;
274
+ var currentOwner = _this.getOwnerMeetingId(sessionId);
275
+ var isOwner = !currentOwner || !ownerMeetingId || currentOwner === ownerMeetingId;
276
+ return {
277
+ currentOwner: currentOwner,
278
+ isOwner: isOwner
279
+ };
280
+ });
242
281
  /**
243
282
  * Get data channel token for the connection
244
- * @param {DataChannelTokenType} dataChannelTokenType
245
- * @returns {string} data channel token
283
+ * @param {DataChannelTokenType|string} tokenKey
284
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting read ownership
285
+ * @returns {string | undefined} data channel token
246
286
  */
247
- (0, _defineProperty3.default)(_this, "getDatachannelToken", function () {
248
- var dataChannelTokenType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _llm.DataChannelTokenType.Default;
249
- return _this.datachannelTokens[dataChannelTokenType];
287
+ (0, _defineProperty3.default)(_this, "getDatachannelToken", function (tokenKey, ownerMeetingId) {
288
+ var resolvedTokenKey = tokenKey !== null && tokenKey !== void 0 ? tokenKey : _llm.DataChannelTokenType.Default;
289
+ var _this$resolveSessionO = _this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey),
290
+ currentOwner = _this$resolveSessionO.currentOwner,
291
+ isOwner = _this$resolveSessionO.isOwner;
292
+ if (!isOwner) {
293
+ _this.logger.info("llm#getDatachannelToken --> skip read for session ".concat(resolvedTokenKey, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
294
+ return undefined;
295
+ }
296
+ return _this.datachannelTokens[resolvedTokenKey];
250
297
  });
251
298
  /**
252
299
  * Set data channel token for the connection
253
300
  * @param {string} datachannelToken - data channel token
254
- * @param {DataChannelTokenType} dataChannelTokenType
301
+ * @param {DataChannelTokenType|string} [tokenKey]
302
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting write ownership
303
+ * @returns {void}
304
+ */
305
+ (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken, tokenKey, ownerMeetingId) {
306
+ var resolvedTokenKey = tokenKey !== null && tokenKey !== void 0 ? tokenKey : _llm.DataChannelTokenType.Default;
307
+ var _this$resolveSessionO2 = _this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey),
308
+ currentOwner = _this$resolveSessionO2.currentOwner,
309
+ isOwner = _this$resolveSessionO2.isOwner;
310
+ if (!isOwner) {
311
+ _this.logger.info("llm#setDatachannelToken --> skip write for session ".concat(resolvedTokenKey, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
312
+ return;
313
+ }
314
+ _this.datachannelTokens[resolvedTokenKey] = datachannelToken;
315
+ });
316
+ /**
317
+ * Clears a single session's data channel token.
318
+ * @param {DataChannelTokenType|string} tokenKey
319
+ * @param {string} ownerMeetingId - Meeting id asserting delete ownership
255
320
  * @returns {void}
256
321
  */
257
- (0, _defineProperty3.default)(_this, "setDatachannelToken", function (datachannelToken) {
258
- var dataChannelTokenType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _llm.DataChannelTokenType.Default;
259
- _this.datachannelTokens[dataChannelTokenType] = datachannelToken;
322
+ (0, _defineProperty3.default)(_this, "clearDatachannelToken", function (tokenKey, ownerMeetingId) {
323
+ var resolvedTokenKey = tokenKey;
324
+ var _this$resolveSessionO3 = _this.resolveSessionOwnership(ownerMeetingId, resolvedTokenKey),
325
+ currentOwner = _this$resolveSessionO3.currentOwner,
326
+ isOwner = _this$resolveSessionO3.isOwner;
327
+ if (!isOwner) {
328
+ _this.logger.info("llm#clearDatachannelToken --> skip clear for session ".concat(resolvedTokenKey, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
329
+ return;
330
+ }
331
+ _this.datachannelTokens[resolvedTokenKey] = undefined;
332
+ delete _this.datachannelTokens[resolvedTokenKey];
260
333
  });
261
334
  /**
262
335
  * Disconnects websocket connection
263
336
  * @param {{code: number, reason: string}} options - The disconnect option object with code and reason
264
337
  * @param {string} sessionId - Connection identifier
265
- * @returns {Promise<void>}
338
+ * @param {string} ownerMeetingId - Meeting id asserting disconnect ownership
339
+ * @returns {Promise<boolean>} True when disconnect was performed, false when skipped
266
340
  */
267
- (0, _defineProperty3.default)(_this, "disconnectLLM", function (options) {
268
- var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _constants.LLM_DEFAULT_SESSION;
269
- return _this.disconnect(options, sessionId).then(function () {
341
+ (0, _defineProperty3.default)(_this, "disconnectLLM", function (options, sessionId, ownerMeetingId) {
342
+ var resolvedSessionId = sessionId !== null && sessionId !== void 0 ? sessionId : _constants.LLM_DEFAULT_SESSION;
343
+
344
+ // Backward-compat path: historically callers could omit ownerMeetingId
345
+ // (and sometimes sessionId). Reuse current owner when available so legacy
346
+ // calls remain best-effort without throwing at teardown time.
347
+ var resolvedOwnerMeetingId = ownerMeetingId || _this.getOwnerMeetingId(resolvedSessionId);
348
+ if (!ownerMeetingId) {
349
+ _this.logger.warn("llm#disconnectLLM --> ownerMeetingId is omitted for session ".concat(resolvedSessionId, "; using legacy compatibility path"));
350
+ }
351
+ var _this$resolveSessionO4 = _this.resolveSessionOwnership(resolvedOwnerMeetingId, resolvedSessionId),
352
+ currentOwner = _this$resolveSessionO4.currentOwner,
353
+ isOwner = _this$resolveSessionO4.isOwner;
354
+ if (!isOwner) {
355
+ _this.logger.info("llm#disconnectLLM --> skip disconnect for session ".concat(resolvedSessionId, "; owned by ").concat(currentOwner, ", candidate ").concat(resolvedOwnerMeetingId));
356
+ return _promise.default.resolve(false);
357
+ }
358
+ return _this.disconnect(options, resolvedSessionId).then(function () {
359
+ // Clear owner tag before cleanup to ensure it's not lingering
360
+ // if another meeting claimed it during disconnect
361
+ _this.setOwnerMeetingId(undefined, resolvedSessionId);
362
+
270
363
  // Clean up sessions data
271
- _this.connections.delete(sessionId);
364
+ _this.connections.delete(resolvedSessionId);
365
+ return true;
272
366
  });
273
367
  });
274
368
  /**
@@ -293,53 +387,76 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
293
387
  }
294
388
  (0, _inherits2.default)(LLMChannel, _ref);
295
389
  return (0, _createClass2.default)(LLMChannel, [{
296
- key: "resetDatachannelTokens",
390
+ key: "setRefreshHandler",
297
391
  value:
298
- /**
299
- * Resets all data‑channel tokens to their initial undefined values.
300
- * Used when leaving or disconnecting from a meeting.
301
- * @returns {void}
302
- */
303
- function resetDatachannelTokens() {
304
- this.datachannelTokens = (0, _defineProperty3.default)((0, _defineProperty3.default)({}, _llm.DataChannelTokenType.Default, undefined), _llm.DataChannelTokenType.PracticeSession, undefined);
305
- }
306
-
307
392
  /**
308
393
  * Set the handler used to refresh the DataChannel token
309
394
  *
310
395
  * @param {function} handler - Function that returns a refreshed token
396
+ * @param {string} [sessionId] - Connection identifier
397
+ * @param {string | undefined} ownerMeetingId - Meeting id asserting refresh-handler ownership
311
398
  * @returns {void}
312
399
  */
313
- }, {
314
- key: "setRefreshHandler",
315
- value: function setRefreshHandler(handler) {
316
- this.refreshHandler = handler;
400
+ function setRefreshHandler(handler, sessionId, ownerMeetingId) {
401
+ var resolvedSessionId = sessionId !== null && sessionId !== void 0 ? sessionId : _constants.LLM_DEFAULT_SESSION;
402
+ var _this$resolveSessionO5 = this.resolveSessionOwnership(ownerMeetingId, resolvedSessionId),
403
+ currentOwner = _this$resolveSessionO5.currentOwner,
404
+ isOwner = _this$resolveSessionO5.isOwner;
405
+ if (!isOwner) {
406
+ this.logger.info("llm#setRefreshHandler --> skip write for session ".concat(resolvedSessionId, "; owned by ").concat(currentOwner, ", candidate ").concat(ownerMeetingId));
407
+ return;
408
+ }
409
+ var sessionData = this.connections.get(resolvedSessionId);
410
+ if (sessionData) {
411
+ sessionData.refreshHandler = handler;
412
+ if (ownerMeetingId) {
413
+ sessionData.ownerMeetingId = ownerMeetingId;
414
+ }
415
+ return;
416
+ }
417
+
418
+ // Intentionally allow a pre-connection session shape here.
419
+ // Some flows inject refreshHandler before register/connect so token refresh
420
+ // is already wired when the socket lifecycle starts. register()/
421
+ // registerAndConnect() will later fill webSocketUrl/binding/locusUrl/
422
+ // datachannelUrl into this same session entry.
423
+ this.connections.set(resolvedSessionId, {
424
+ refreshHandler: handler,
425
+ ownerMeetingId: ownerMeetingId
426
+ });
317
427
  }
318
428
 
319
429
  /**
320
430
  * Refresh the data channel token using the injected handler.
321
431
  * Logs a descriptive error if the handler is missing or fails.
322
- *
432
+ * @param {string} sessionId - Connection identifier (defaults to default session)
323
433
  * @returns {Promise<string>} The refreshed token.
324
434
  */
325
435
  }, {
326
436
  key: "refreshDataChannelToken",
327
437
  value: (function () {
328
438
  var _refreshDataChannelToken = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3() {
329
- var res, _t;
439
+ var _this$connections$get;
440
+ var sessionId,
441
+ refreshHandler,
442
+ res,
443
+ _args3 = arguments,
444
+ _t;
330
445
  return _regenerator.default.wrap(function (_context3) {
331
446
  while (1) switch (_context3.prev = _context3.next) {
332
447
  case 0:
333
- if (this.refreshHandler) {
448
+ sessionId = _args3.length > 0 && _args3[0] !== undefined ? _args3[0] : _constants.LLM_DEFAULT_SESSION;
449
+ refreshHandler = (_this$connections$get = this.connections.get(sessionId)) === null || _this$connections$get === void 0 ? void 0 : _this$connections$get.refreshHandler;
450
+ if (refreshHandler) {
334
451
  _context3.next = 1;
335
452
  break;
336
453
  }
337
- this.logger.warn('llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh');
454
+ this.logger.warn("llm#refreshDataChannelToken --> LLM refreshHandler is not set for session ".concat(sessionId, ", skipping token refresh"));
338
455
  return _context3.abrupt("return", null);
339
456
  case 1:
340
457
  _context3.prev = 1;
341
458
  _context3.next = 2;
342
- return this.refreshHandler();
459
+ return refreshHandler();
343
460
  case 2:
344
461
  res = _context3.sent;
345
462
  return _context3.abrupt("return", res);
@@ -359,6 +476,74 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
359
476
  }
360
477
  return refreshDataChannelToken;
361
478
  }())
479
+ }, {
480
+ key: "getLocusUrlByDatachannelUrl",
481
+ value:
482
+ /**
483
+ * Look up the locusUrl associated with a datachannel request URL.
484
+ * Iterates all active LLM sessions and returns the locusUrl of the
485
+ * session whose stored datachannelUrl is a prefix of the given request URL.
486
+ *
487
+ * @param {string} requestUrl - The in-flight request URL to match
488
+ * @returns {string | undefined} The matching locusUrl, or undefined if not found
489
+ */
490
+ function getLocusUrlByDatachannelUrl(requestUrl) {
491
+ var _iterator = _createForOfIteratorHelper(this.connections),
492
+ _step;
493
+ try {
494
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
495
+ var _step$value = (0, _slicedToArray2.default)(_step.value, 2),
496
+ connection = _step$value[1];
497
+ if (connection.datachannelUrl && LLMChannel.matchesDatachannelRequestUrl(requestUrl, connection.datachannelUrl)) {
498
+ return connection.locusUrl;
499
+ }
500
+ }
501
+ } catch (err) {
502
+ _iterator.e(err);
503
+ } finally {
504
+ _iterator.f();
505
+ }
506
+ return undefined;
507
+ }
508
+
509
+ /**
510
+ * Look up the sessionId associated with a datachannel request URL.
511
+ * Iterates all active LLM sessions and returns the sessionId whose
512
+ * stored datachannelUrl is a prefix of the given request URL.
513
+ *
514
+ * @param {string} requestUrl - The in-flight request URL to match
515
+ * @returns {string | undefined} The matching sessionId, or undefined if not found
516
+ */
517
+ }, {
518
+ key: "getSessionIdByDatachannelUrl",
519
+ value: function getSessionIdByDatachannelUrl(requestUrl) {
520
+ var _iterator2 = _createForOfIteratorHelper(this.connections),
521
+ _step2;
522
+ try {
523
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
524
+ var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
525
+ sessionId = _step2$value[0],
526
+ connection = _step2$value[1];
527
+ if (connection.datachannelUrl && LLMChannel.matchesDatachannelRequestUrl(requestUrl, connection.datachannelUrl)) {
528
+ return sessionId;
529
+ }
530
+ }
531
+ } catch (err) {
532
+ _iterator2.e(err);
533
+ } finally {
534
+ _iterator2.f();
535
+ }
536
+ return undefined;
537
+ }
538
+
539
+ /**
540
+ * Matches a request URL to a stored datachannel registration URL.
541
+ * Host can differ (e.g. rewritten by hostmap interceptor), so we first
542
+ * try full URL prefix and then fall back to pathname prefix.
543
+ * @param {string} requestUrl
544
+ * @param {string} registrationUrl
545
+ * @returns {boolean}
546
+ */
362
547
  }, {
363
548
  key: "isDataChannelTokenEnabled",
364
549
  value:
@@ -378,6 +563,23 @@ var LLMChannel = exports.default = /*#__PURE__*/function (_ref) {
378
563
  * @param {string[]} subchannels - List of subchannels to declare as subscription-aware.
379
564
  * @returns {string} The final URL with updated query parameters.
380
565
  */
566
+ }], [{
567
+ key: "matchesDatachannelRequestUrl",
568
+ value: function matchesDatachannelRequestUrl(requestUrl, registrationUrl) {
569
+ if (!requestUrl || !registrationUrl) {
570
+ return false;
571
+ }
572
+ if (requestUrl.startsWith(registrationUrl)) {
573
+ return true;
574
+ }
575
+ try {
576
+ var request = new URL(requestUrl);
577
+ var registration = new URL(registrationUrl);
578
+ return request.pathname.startsWith(registration.pathname);
579
+ } catch (error) {
580
+ return false;
581
+ }
582
+ }
381
583
  }]);
382
584
  }(_internalPluginMercury.default);
383
585
  (0, _defineProperty3.default)(LLMChannel, "buildUrlWithAwareSubchannels", function (baseUrl, subchannels) {