Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3c96269
feat(webinar): refactor mercury/llm to support multiple connections
mickelr Sep 10, 2025
6406e7c
feat(webinar): refactor mercury/llm to support multiple connections
mickelr Sep 11, 2025
af94e46
Merge remote-tracking branch 'origin/next' into feat/mulitLLMConnections
mickelr Sep 16, 2025
17e7e26
feat(webinar): refactor mercury/llm to support multiple connections
mickelr Sep 16, 2025
d78f99a
Merge remote-tracking branch 'origin/next' into feat/mulitLLMConnections
mickelr Sep 22, 2025
6ca77ca
feat(webinar): modify unit test
mickelr Sep 23, 2025
3ae1629
Merge remote-tracking branch 'origin/next' into feat/mulitLLMConnections
mickelr Oct 9, 2025
694a6e0
feat(multipleLLM): make unit test work for mercury and llm
mickelr Oct 9, 2025
08a72ce
feat(multipleLLM): for deploy feature branch
mickelr Oct 9, 2025
58598b0
feat(multipleLLM): update log
mickelr Oct 14, 2025
9540c09
Merge branch 'next' into multiple-llm
mickelr Oct 14, 2025
9fde6fb
Merge remote-tracking branch 'webex/next' into multiple-llm
mickelr Oct 14, 2025
21513c8
Merge remote-tracking branch 'webex/multiple-llm' into multiple-llm
mickelr Oct 14, 2025
8f45ed4
feat(multipleLLM): force publish (#4536)
mickelr Oct 14, 2025
d3a9a95
Merge branch 'next' into multiple-llm
mickelr Oct 14, 2025
4f8525c
feat(multipleLLM): force publish
mickelr Oct 14, 2025
99600bf
Merge remote-tracking branch 'webex/multiple-llm' into multiple-llm
mickelr Oct 14, 2025
423d3f9
feat(multipleLLM): force publish
mickelr Oct 14, 2025
dd948a8
Merge branch 'next' into multiple-llm
mickelr Oct 15, 2025
eed15cf
Merge branch 'next' into multiple-llm
mickelr Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
push:
branches: # White-list of deployable tags and branches. Note that all white-listed branches cannot include any `/` characters
- next
- multiple-llm

env:
rid: ${{ github.run_id }}-${{ github.run_number }}
Expand Down Expand Up @@ -258,7 +259,7 @@ jobs:
git config user.name "${GIT_AUTHOR_NAME}"

- name: Get existing changelog from documentation Branch
run: |
run: |
git fetch origin documentation
git checkout origin/documentation -- docs/changelog/logs

Expand Down
2 changes: 2 additions & 0 deletions packages/@webex/internal-plugin-llm/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
// eslint-disable-next-line import/prefer-default-export
export const LLM = 'llm';

export const LLM_DEFAULT_SESSION = 'llm-default-session';
130 changes: 97 additions & 33 deletions packages/@webex/internal-plugin-llm/src/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Mercury from '@webex/internal-plugin-mercury';

import {LLM} from './constants';
import {LLM, LLM_DEFAULT_SESSION} from './constants';
// eslint-disable-next-line no-unused-vars
import {ILLMChannel} from './llm.types';

Expand Down Expand Up @@ -42,90 +42,154 @@ export const config = {
*/
export default class LLMChannel extends (Mercury as any) implements ILLMChannel {
namespace = LLM;

defaultSessionId = LLM_DEFAULT_SESSION;
/**
* If the LLM plugin has been registered and listening
* @instance
* @type {Boolean}
* @public
* Map to store connection-specific data for multiple LLM connections
* @private
* @type {Map<string, {webSocketUrl?: string; binding?: string; locusUrl?: string; datachannelUrl?: string}>}
*/

private webSocketUrl?: string;

private binding?: string;

private locusUrl?: string;

private datachannelUrl?: string;
private connections: Map<
string,
{
webSocketUrl?: string;
binding?: string;
locusUrl?: string;
datachannelUrl?: string;
}
> = new Map();

/**
* Register to the websocket
* @param {string} llmSocketUrl
* @param {string} sessionId - Connection identifier
* @returns {Promise<void>}
*/
private register = (llmSocketUrl: string): Promise<void> =>
private register = (
llmSocketUrl: string,
sessionId: string = LLM_DEFAULT_SESSION
): Promise<void> =>
this.request({
method: 'POST',
url: llmSocketUrl,
body: {deviceUrl: this.webex.internal.device.url},
})
.then((res: {body: {webSocketUrl: string; binding: string}}) => {
this.webSocketUrl = res.body.webSocketUrl;
this.binding = res.body.binding;
// Get or create connection data
const sessionData = this.connections.get(sessionId) || {};
sessionData.webSocketUrl = res.body.webSocketUrl;
sessionData.binding = res.body.binding;
this.connections.set(sessionId, sessionData);
})
.catch((error: any) => {
this.logger.error(`Error connecting to websocket: ${error}`);
this.logger.error(`Error connecting to websocket for ${sessionId}: ${error}`);
throw error;
});

/**
* Register and connect to the websocket
* @param {string} locusUrl
* @param {string} datachannelUrl
* @param {string} sessionId - Connection identifier
* @returns {Promise<void>}
*/
public registerAndConnect = (locusUrl: string, datachannelUrl: string): Promise<void> =>
this.register(datachannelUrl).then(() => {
public registerAndConnect = (
locusUrl: string,
datachannelUrl: string,
sessionId: string = LLM_DEFAULT_SESSION
): Promise<void> =>
this.register(datachannelUrl, sessionId).then(() => {
if (!locusUrl || !datachannelUrl) return undefined;
this.locusUrl = locusUrl;
this.datachannelUrl = datachannelUrl;
this.connect(this.webSocketUrl);

// Get or create connection data
const sessionData = this.connections.get(sessionId) || {};
sessionData.locusUrl = locusUrl;
sessionData.datachannelUrl = datachannelUrl;
this.connections.set(sessionId, sessionData);

return this.connect(sessionData.webSocketUrl, sessionId);
});

/**
* Tells if LLM socket is connected
* @param {string} sessionId - Connection identifier
* @returns {boolean} connected
*/
public isConnected = (): boolean => this.connected;
public isConnected = (sessionId = LLM_DEFAULT_SESSION): boolean => {
const socket = this.getSocket(sessionId);

return socket ? socket.connected : false;
};

/**
* Tells if LLM socket is binding
* @param {string} sessionId - Connection identifier
* @returns {string} binding
*/
public getBinding = (): string => this.binding;
public getBinding = (sessionId = LLM_DEFAULT_SESSION): string => {
const sessionData = this.connections.get(sessionId);

return sessionData?.binding;
};

/**
* Get Locus URL for the connection
* @param {string} sessionId - Connection identifier
* @returns {string} locus Url
*/
public getLocusUrl = (): string => this.locusUrl;
public getLocusUrl = (sessionId = LLM_DEFAULT_SESSION): string => {
const sessionData = this.connections.get(sessionId);

return sessionData?.locusUrl;
};

/**
* Get data channel URL for the connection
* @param {string} sessionId - Connection identifier
* @returns {string} data channel Url
*/
public getDatachannelUrl = (): string => this.datachannelUrl;
public getDatachannelUrl = (sessionId = LLM_DEFAULT_SESSION): string => {
const sessionData = this.connections.get(sessionId);

return sessionData?.datachannelUrl;
};

/**
* Disconnects websocket connection
* @param {{code: number, reason: string}} options - The disconnect option object with code and reason
* @param {string} sessionId - Connection identifier
* @returns {Promise<void>}
*/
public disconnectLLM = (
options: {code: number; reason: string},
sessionId: string = LLM_DEFAULT_SESSION
): Promise<void> =>
this.disconnect(options, sessionId).then(() => {
// Clean up sessions data
this.connections.delete(sessionId);
});

/**
* Disconnects all LLM websocket connections
* @param {{code: number, reason: string}} options - The disconnect option object with code and reason
* @returns {Promise<void>}
*/
public disconnectLLM = (options: object): Promise<void> =>
this.disconnect(options).then(() => {
this.locusUrl = undefined;
this.datachannelUrl = undefined;
this.binding = undefined;
this.webSocketUrl = undefined;
public disconnectAllLLM = (options?: {code: number; reason: string}): Promise<void> =>
this.disconnectAll(options).then(() => {
// Clean up all connection data
this.connections.clear();
});

/**
* Get all active LLM connections
* @returns {Map} Map of sessionId to session data
*/
public getAllConnections = (): Map<
string,
{
webSocketUrl?: string;
binding?: string;
locusUrl?: string;
datachannelUrl?: string;
}
> => new Map(this.connections);
}
25 changes: 20 additions & 5 deletions packages/@webex/internal-plugin-llm/src/llm.types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
interface ILLMChannel {
registerAndConnect: (locusUrl: string, datachannelUrl: string) => Promise<void>;
isConnected: () => boolean;
getBinding: () => string;
getLocusUrl: () => string;
disconnectLLM: (options: {code: number; reason: string}) => Promise<void>;
registerAndConnect: (
locusUrl: string,
datachannelUrl: string,
sessionId?: string
) => Promise<void>;
isConnected: (sessionId?: string) => boolean;
getBinding: (sessionId?: string) => string;
getLocusUrl: (sessionId?: string) => string;
getDatachannelUrl: (sessionId?: string) => string;
disconnectLLM: (options: {code: number; reason: string}, sessionId?: string) => Promise<void>;
disconnectAllLLM: (options?: {code: number; reason: string}) => Promise<void>;
getAllConnections: () => Map<
string,
{
webSocketUrl?: string;
binding?: string;
locusUrl?: string;
datachannelUrl?: string;
}
>;
}
// eslint-disable-next-line import/prefer-default-export
export type {ILLMChannel};
3 changes: 2 additions & 1 deletion packages/@webex/internal-plugin-llm/test/unit/spec/llm.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ describe('plugin-llm', () => {

llmService = webex.internal.llm;
llmService.connect = sinon.stub().callsFake(() => {
llmService.connected = true;
// Simulate a successful connection by stubbing getSocket to return connected: true
llmService.getSocket = sinon.stub().returns({connected: true});
});
llmService.disconnect = sinon.stub().resolves(true);
llmService.request = sinon.stub().resolves({
Expand Down
Loading
Loading