Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
- multi-llms

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