@egain/ai-agent-sdk 0.1.0

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.
Files changed (164) hide show
  1. package/README.md +295 -0
  2. package/dist/browser.js +22739 -0
  3. package/dist/core/AiAgent.d.ts +1126 -0
  4. package/dist/core/AiAgent.d.ts.map +1 -0
  5. package/dist/core/AiAgent.js +2037 -0
  6. package/dist/core/AiAgent.js.map +1 -0
  7. package/dist/core/api/ApiHelper.d.ts +433 -0
  8. package/dist/core/api/ApiHelper.d.ts.map +1 -0
  9. package/dist/core/api/ApiHelper.js +689 -0
  10. package/dist/core/api/ApiHelper.js.map +1 -0
  11. package/dist/core/api/CacheAdapter.d.ts +295 -0
  12. package/dist/core/api/CacheAdapter.d.ts.map +1 -0
  13. package/dist/core/api/CacheAdapter.js +298 -0
  14. package/dist/core/api/CacheAdapter.js.map +1 -0
  15. package/dist/core/auth/AnonymousAuthStrategy.d.ts +87 -0
  16. package/dist/core/auth/AnonymousAuthStrategy.d.ts.map +1 -0
  17. package/dist/core/auth/AnonymousAuthStrategy.js +257 -0
  18. package/dist/core/auth/AnonymousAuthStrategy.js.map +1 -0
  19. package/dist/core/auth/AuthProvider.d.ts +13 -0
  20. package/dist/core/auth/AuthProvider.d.ts.map +1 -0
  21. package/dist/core/auth/AuthProvider.js +2 -0
  22. package/dist/core/auth/AuthProvider.js.map +1 -0
  23. package/dist/core/auth/AuthStrategy.d.ts +74 -0
  24. package/dist/core/auth/AuthStrategy.d.ts.map +1 -0
  25. package/dist/core/auth/AuthStrategy.js +2 -0
  26. package/dist/core/auth/AuthStrategy.js.map +1 -0
  27. package/dist/core/auth/AuthenticationService.d.ts +226 -0
  28. package/dist/core/auth/AuthenticationService.d.ts.map +1 -0
  29. package/dist/core/auth/AuthenticationService.js +344 -0
  30. package/dist/core/auth/AuthenticationService.js.map +1 -0
  31. package/dist/core/auth/ClientCredentialsAuthStrategy.d.ts +62 -0
  32. package/dist/core/auth/ClientCredentialsAuthStrategy.d.ts.map +1 -0
  33. package/dist/core/auth/ClientCredentialsAuthStrategy.js +78 -0
  34. package/dist/core/auth/ClientCredentialsAuthStrategy.js.map +1 -0
  35. package/dist/core/auth/PKCEAuthStrategy.d.ts +136 -0
  36. package/dist/core/auth/PKCEAuthStrategy.d.ts.map +1 -0
  37. package/dist/core/auth/PKCEAuthStrategy.js +409 -0
  38. package/dist/core/auth/PKCEAuthStrategy.js.map +1 -0
  39. package/dist/core/auth/PreAuthStrategy.d.ts +101 -0
  40. package/dist/core/auth/PreAuthStrategy.d.ts.map +1 -0
  41. package/dist/core/auth/PreAuthStrategy.js +216 -0
  42. package/dist/core/auth/PreAuthStrategy.js.map +1 -0
  43. package/dist/core/auth/msal-browser.js +19683 -0
  44. package/dist/core/auth/msal-loader.d.ts +14 -0
  45. package/dist/core/auth/msal-loader.d.ts.map +1 -0
  46. package/dist/core/auth/msal-loader.js +43 -0
  47. package/dist/core/auth/msal-loader.js.map +1 -0
  48. package/dist/core/connection/Connection.d.ts +168 -0
  49. package/dist/core/connection/Connection.d.ts.map +1 -0
  50. package/dist/core/connection/Connection.js +290 -0
  51. package/dist/core/connection/Connection.js.map +1 -0
  52. package/dist/core/connection/ConnectionState.d.ts +11 -0
  53. package/dist/core/connection/ConnectionState.d.ts.map +1 -0
  54. package/dist/core/connection/ConnectionState.js +12 -0
  55. package/dist/core/connection/ConnectionState.js.map +1 -0
  56. package/dist/core/connection/Transport.d.ts +98 -0
  57. package/dist/core/connection/Transport.d.ts.map +1 -0
  58. package/dist/core/connection/Transport.js +27 -0
  59. package/dist/core/connection/Transport.js.map +1 -0
  60. package/dist/core/connection/WebSocketTransport.d.ts +65 -0
  61. package/dist/core/connection/WebSocketTransport.d.ts.map +1 -0
  62. package/dist/core/connection/WebSocketTransport.js +177 -0
  63. package/dist/core/connection/WebSocketTransport.js.map +1 -0
  64. package/dist/core/errors/SDKError.d.ts +27 -0
  65. package/dist/core/errors/SDKError.d.ts.map +1 -0
  66. package/dist/core/errors/SDKError.js +43 -0
  67. package/dist/core/errors/SDKError.js.map +1 -0
  68. package/dist/core/events/EventEmitter.d.ts +120 -0
  69. package/dist/core/events/EventEmitter.d.ts.map +1 -0
  70. package/dist/core/events/EventEmitter.js +183 -0
  71. package/dist/core/events/EventEmitter.js.map +1 -0
  72. package/dist/core/logging/LogLevel.d.ts +33 -0
  73. package/dist/core/logging/LogLevel.d.ts.map +1 -0
  74. package/dist/core/logging/LogLevel.js +40 -0
  75. package/dist/core/logging/LogLevel.js.map +1 -0
  76. package/dist/core/logging/Logger.d.ts +120 -0
  77. package/dist/core/logging/Logger.d.ts.map +1 -0
  78. package/dist/core/logging/Logger.js +204 -0
  79. package/dist/core/logging/Logger.js.map +1 -0
  80. package/dist/core/logging/globalLogger.d.ts +8 -0
  81. package/dist/core/logging/globalLogger.d.ts.map +1 -0
  82. package/dist/core/logging/globalLogger.js +12 -0
  83. package/dist/core/logging/globalLogger.js.map +1 -0
  84. package/dist/core/logging/types.d.ts +45 -0
  85. package/dist/core/logging/types.d.ts.map +1 -0
  86. package/dist/core/logging/types.js +2 -0
  87. package/dist/core/logging/types.js.map +1 -0
  88. package/dist/core/message/BaseMessageHandler.d.ts +208 -0
  89. package/dist/core/message/BaseMessageHandler.d.ts.map +1 -0
  90. package/dist/core/message/BaseMessageHandler.js +155 -0
  91. package/dist/core/message/BaseMessageHandler.js.map +1 -0
  92. package/dist/core/message/Message.d.ts +69 -0
  93. package/dist/core/message/Message.d.ts.map +1 -0
  94. package/dist/core/message/Message.js +131 -0
  95. package/dist/core/message/Message.js.map +1 -0
  96. package/dist/core/message/MessageProcessor.d.ts +51 -0
  97. package/dist/core/message/MessageProcessor.d.ts.map +1 -0
  98. package/dist/core/message/MessageProcessor.js +123 -0
  99. package/dist/core/message/MessageProcessor.js.map +1 -0
  100. package/dist/core/message/MessageTypes.d.ts +123 -0
  101. package/dist/core/message/MessageTypes.d.ts.map +1 -0
  102. package/dist/core/message/MessageTypes.js +106 -0
  103. package/dist/core/message/MessageTypes.js.map +1 -0
  104. package/dist/core/message/Transcript.d.ts +373 -0
  105. package/dist/core/message/Transcript.d.ts.map +1 -0
  106. package/dist/core/message/Transcript.js +355 -0
  107. package/dist/core/message/Transcript.js.map +1 -0
  108. package/dist/core/message/handlers/AgentMessageHandler.d.ts +26 -0
  109. package/dist/core/message/handlers/AgentMessageHandler.d.ts.map +1 -0
  110. package/dist/core/message/handlers/AgentMessageHandler.js +130 -0
  111. package/dist/core/message/handlers/AgentMessageHandler.js.map +1 -0
  112. package/dist/core/message/handlers/ChatHistoryHandler.d.ts +12 -0
  113. package/dist/core/message/handlers/ChatHistoryHandler.d.ts.map +1 -0
  114. package/dist/core/message/handlers/ChatHistoryHandler.js +49 -0
  115. package/dist/core/message/handlers/ChatHistoryHandler.js.map +1 -0
  116. package/dist/core/message/handlers/ErrorMessageHandler.d.ts +12 -0
  117. package/dist/core/message/handlers/ErrorMessageHandler.d.ts.map +1 -0
  118. package/dist/core/message/handlers/ErrorMessageHandler.js +49 -0
  119. package/dist/core/message/handlers/ErrorMessageHandler.js.map +1 -0
  120. package/dist/core/message/handlers/HeartbeatHandler.d.ts +12 -0
  121. package/dist/core/message/handlers/HeartbeatHandler.d.ts.map +1 -0
  122. package/dist/core/message/handlers/HeartbeatHandler.js +46 -0
  123. package/dist/core/message/handlers/HeartbeatHandler.js.map +1 -0
  124. package/dist/core/message/handlers/TokenRefreshHandler.d.ts +30 -0
  125. package/dist/core/message/handlers/TokenRefreshHandler.d.ts.map +1 -0
  126. package/dist/core/message/handlers/TokenRefreshHandler.js +84 -0
  127. package/dist/core/message/handlers/TokenRefreshHandler.js.map +1 -0
  128. package/dist/core/message/types.d.ts +107 -0
  129. package/dist/core/message/types.d.ts.map +1 -0
  130. package/dist/core/message/types.js +30 -0
  131. package/dist/core/message/types.js.map +1 -0
  132. package/dist/core/platform/HookContract.d.ts +112 -0
  133. package/dist/core/platform/HookContract.d.ts.map +1 -0
  134. package/dist/core/platform/HookContract.js +13 -0
  135. package/dist/core/platform/HookContract.js.map +1 -0
  136. package/dist/core/platform/PlatformComponentService.d.ts +40 -0
  137. package/dist/core/platform/PlatformComponentService.d.ts.map +1 -0
  138. package/dist/core/platform/PlatformComponentService.js +12 -0
  139. package/dist/core/platform/PlatformComponentService.js.map +1 -0
  140. package/dist/core/platform/PlatformScriptLoader.d.ts +41 -0
  141. package/dist/core/platform/PlatformScriptLoader.d.ts.map +1 -0
  142. package/dist/core/platform/PlatformScriptLoader.js +110 -0
  143. package/dist/core/platform/PlatformScriptLoader.js.map +1 -0
  144. package/dist/core/polyfills.d.ts +16 -0
  145. package/dist/core/polyfills.d.ts.map +1 -0
  146. package/dist/core/polyfills.js +168 -0
  147. package/dist/core/polyfills.js.map +1 -0
  148. package/dist/core/portal-initializer/PortalInitializer.d.ts +234 -0
  149. package/dist/core/portal-initializer/PortalInitializer.d.ts.map +1 -0
  150. package/dist/core/portal-initializer/PortalInitializer.js +636 -0
  151. package/dist/core/portal-initializer/PortalInitializer.js.map +1 -0
  152. package/dist/core/queue/MessageQueue.d.ts +277 -0
  153. package/dist/core/queue/MessageQueue.d.ts.map +1 -0
  154. package/dist/core/queue/MessageQueue.js +291 -0
  155. package/dist/core/queue/MessageQueue.js.map +1 -0
  156. package/dist/core/types/PortalTypes.d.ts +51 -0
  157. package/dist/core/types/PortalTypes.d.ts.map +1 -0
  158. package/dist/core/types/PortalTypes.js +8 -0
  159. package/dist/core/types/PortalTypes.js.map +1 -0
  160. package/dist/index.d.ts +91 -0
  161. package/dist/index.d.ts.map +1 -0
  162. package/dist/index.js +82 -0
  163. package/dist/index.js.map +1 -0
  164. package/package.json +84 -0
@@ -0,0 +1,2037 @@
1
+ import { Connection } from './connection/Connection.js';
2
+ import { MessageQueue } from './queue/MessageQueue.js';
3
+ import { AuthenticationService } from './auth/AuthenticationService.js';
4
+ import { PKCEAuthStrategy } from './auth/PKCEAuthStrategy.js';
5
+ import { EventEmitter } from './events/EventEmitter.js';
6
+ import { ConnectionError, MessageError } from './errors/SDKError.js';
7
+ import { ApiHelper } from './api/ApiHelper.js';
8
+ import { createCacheAdapter } from './api/CacheAdapter.js';
9
+ import { MessageProcessor } from './message/MessageProcessor.js';
10
+ import { Message } from './message/Message.js';
11
+ import { PERSONA, ROLE } from './message/types.js';
12
+ import { TokenRefreshHandler } from './message/handlers/TokenRefreshHandler.js';
13
+ import { Logger } from './logging/Logger.js';
14
+ import { LogLevel } from './logging/LogLevel.js';
15
+ import { createGracefulDisconnectMessage, createTokenMessage, createContextMessage } from './message/MessageTypes.js';
16
+ import { Transcript } from './message/Transcript.js';
17
+ import { PortalInitializer } from './portal-initializer/PortalInitializer.js';
18
+ import { loadPlatformScript, deriveEnvironment } from './platform/PlatformScriptLoader.js';
19
+ /**
20
+ * Cache key prefix for context storage
21
+ */
22
+ const CONTEXT_CACHE_KEY_PREFIX = 'egain_aiagent_context_';
23
+ /** Session-scoped cache key prefix for profile list (restart reuse). Suffix: agentId_portalId. */
24
+ const PIPELINE_PROFILES_CACHE_KEY_PREFIX = 'eg_profiles_';
25
+ /**
26
+ * Main class for interacting with the eGain AI Agent platform.
27
+ *
28
+ * The AiAgent class provides:
29
+ * - WebSocket connection management with automatic reconnection
30
+ * - Message queuing when offline
31
+ * - Event-driven communication
32
+ * - Transcript management
33
+ * - Context persistence
34
+ *
35
+ * **Initialization flows**
36
+ *
37
+ * After authentication, one of two paths runs:
38
+ *
39
+ * - **Direct flow** (non–contact-center agents): fetches session, creates the WebSocket connection,
40
+ * emits `initialized`. With {@link AiAgentConfig.autoConnect}, `connect()` runs automatically.
41
+ * - **Contact Center (CC) flow** (contact-center agents, per API `agentType` / authenticated agents
42
+ * with legacy empty type): runs a REST-only portal → (optional agent) → profile pipeline, then
43
+ * emits `initialized`. The WebSocket is created when you call `connect()` (or automatically if
44
+ * `autoConnect` is true after the pipeline completes).
45
+ *
46
+ * **Flow A (specific agent)** — Use the target agent ID in {@link AiAgentConfig.id}. CC pipeline:
47
+ * portal selection → profile selection.
48
+ *
49
+ * **Flow B (default agent / agent selection)** — Set `initParams: { isDefaultAgent: "true" }`.
50
+ * CC pipeline: portal → agent → profile. The selected agent becomes the chat identity
51
+ * (`resolvedAgentId`); subsequent session and chat use that ID, not the bootstrap `config.id`.
52
+ *
53
+ * When the CC pipeline has multiple options at a step, it emits `portalsAvailable`,
54
+ * `agentsAvailable`, or `profilesAvailable`. Call {@link AiAgent.selectPortal},
55
+ * {@link AiAgent.selectAgent}, or {@link AiAgent.selectUserProfile} to continue. After the
56
+ * `initialized` event, call {@link AiAgent.connect} unless `autoConnect` already connected you.
57
+ *
58
+ * @example Direct flow (typical non–Contact Center agent)
59
+ * ```typescript
60
+ * import { AiAgent } from "@eGain/ai-agent-sdk";
61
+ *
62
+ * const agent = new AiAgent({
63
+ * id: "agent-id",
64
+ * endpoint: "https://api.egain.com",
65
+ * auth: { type: "pre-auth", accessToken: "your-access-token" },
66
+ * autoConnect: true,
67
+ * });
68
+ *
69
+ * agent.on("agentMessage", (event) => {
70
+ * console.log("Agent:", event.payload.message?.content);
71
+ * });
72
+ *
73
+ * await agent.initialize();
74
+ * await agent.send("Hello!");
75
+ * ```
76
+ *
77
+ * @example Contact Center flow (register handlers before `initialize`)
78
+ * ```typescript
79
+ * const agent = new AiAgent({
80
+ * id: "agent-id",
81
+ * endpoint: "https://api.egain.com",
82
+ * auth: { type: "pkce", config: { ... } },
83
+ * initParams: { userid: "user-123" },
84
+ * });
85
+ *
86
+ * agent.on("portalsAvailable", (e) => agent.selectPortal(e.payload.portals[0]));
87
+ * agent.on("agentsAvailable", (e) => agent.selectAgent(e.payload.agents[0]));
88
+ * agent.on("profilesAvailable", (e) => agent.selectUserProfile(e.payload.profiles[0]));
89
+ * agent.on("initialized", async () => {
90
+ * await agent.connect();
91
+ * });
92
+ * await agent.initialize();
93
+ * ```
94
+ *
95
+ * @example With context
96
+ * ```typescript
97
+ * import { AiAgent, createContextMessage } from "@eGain/ai-agent-sdk";
98
+ *
99
+ * await agent.send(createContextMessage({
100
+ * context: { userId: "123", accountType: "premium" },
101
+ * }));
102
+ * ```
103
+ *
104
+ * @category Core
105
+ * @see {@link AiAgentConfig} for configuration options
106
+ * @see {@link AgentEvents} for available events
107
+ */
108
+ export class AiAgent extends EventEmitter {
109
+ constructor(config) {
110
+ super();
111
+ /** True when the agent completed the portal initialization pipeline (needed for restart guard). */
112
+ this.completedPortalPipeline = false;
113
+ // --- Platform connector state (Phase 2) ---
114
+ this.callerInfo = null;
115
+ this.userContext = null;
116
+ this.platformToken = null;
117
+ this.isPlatformAuthenticated = false;
118
+ this.conversationId = null;
119
+ this.filterTags = {};
120
+ this.callTranscript = [];
121
+ this.userDetails = null;
122
+ /** True after `addCustomAuthScopes` merged scopes into `config.scopes` (PKCE/hooks must use that list). */
123
+ this.authScopesAugmentedByPlatform = false;
124
+ /**
125
+ * Post-authentication callback. Runs the portal initializer when
126
+ * {@link shouldRunPortalInitializationPipeline} is true; otherwise session + connection (direct flow).
127
+ * @param accessToken - The access token to use for authentication
128
+ */
129
+ this.onAuthComplete = async (accessToken) => {
130
+ const runPortalPipeline = this.shouldRunPortalInitializationPipeline();
131
+ this.logger.debug('onAuthComplete callback invoked', {
132
+ agentId: this.config.id,
133
+ agentType: this.agentDetails?.agentType,
134
+ runPortalPipeline,
135
+ instanceId: this._instanceId,
136
+ });
137
+ await this.fetchUserOrCustomerDetails(accessToken);
138
+ if (runPortalPipeline) {
139
+ this.logger.info('Running portal initializer pipeline', { agentId: this.config.id });
140
+ await this.runPortalInitializerPipeline(accessToken);
141
+ }
142
+ else {
143
+ this.logger.info('Running direct initialization pipeline', { agentId: this.config.id });
144
+ await this.runDirectInitializationAfterAuth(accessToken);
145
+ }
146
+ this.logger.debug('onAuthComplete completed', {
147
+ agentId: this.resolvedAgentId,
148
+ isInitialized: this.isInitialized,
149
+ });
150
+ };
151
+ this.isFlushingQueue = false;
152
+ this.isInitialized = false;
153
+ this.config = config;
154
+ this.resolvedAgentId = config.id;
155
+ this.isAgentSelectionMode = (config.initParams?.isDefaultAgent?.toLowerCase() ?? '') === 'true';
156
+ this.initParams = config.initParams ?? {};
157
+ // Debug: track instance identity
158
+ this._instanceId = Math.random().toString(36).substring(7);
159
+ // Validate configuration
160
+ if (!config.endpoint) {
161
+ throw new Error('Endpoint is required');
162
+ }
163
+ if (!config.id) {
164
+ throw new Error('Agent ID is required');
165
+ }
166
+ // Store sessionId from config if provided
167
+ if (config.sessionId !== undefined) {
168
+ this.sessionId = config.sessionId;
169
+ // Note: Logger will be initialized below and will use this.sessionId in contextProvider
170
+ }
171
+ // Initialize logger
172
+ if (config.logger) {
173
+ this.logger = config.logger;
174
+ // Note: If a custom logger is provided, sessionId won't be automatically included
175
+ // unless the custom logger has its own contextProvider configured
176
+ }
177
+ else {
178
+ this.logger = new Logger({
179
+ level: config.logLevel ?? LogLevel.INFO,
180
+ enableConsole: config.enableLogging ?? true,
181
+ name: `AiAgent:${config.id}`,
182
+ contextProvider: () => ({
183
+ sessionId: this.sessionId,
184
+ }),
185
+ });
186
+ }
187
+ // Log if sessionId was provided in config
188
+ if (config.sessionId !== undefined) {
189
+ this.logger.debug('SessionId provided in config, will skip network fetch', {
190
+ sessionId: this.sessionId,
191
+ agentId: config.id
192
+ });
193
+ }
194
+ // Initialize message queue
195
+ this.messageQueue = new MessageQueue(config.maxQueueSize ?? 1000);
196
+ // Initialize message processor
197
+ this.messageProcessor = new MessageProcessor(this.logger.createChild('MessageProcessor'));
198
+ // Initialize transcript
199
+ this.transcript = new Transcript(config.transcriptConfig);
200
+ // Create authentication service - handles all input types internally
201
+ // Pass cache config so AnonymousAuthStrategy can use it for caching metadata
202
+ this.authService = new AuthenticationService(config.auth, this.logger.createChild('AuthenticationService'), config.cache);
203
+ // Initialize context cache adapter
204
+ // Use custom adapter if provided in cache config, otherwise create based on storage type
205
+ if (config.cache?.adapter) {
206
+ this.contextCacheAdapter = config.cache.adapter;
207
+ }
208
+ else {
209
+ this.contextCacheAdapter = createCacheAdapter(config.cache?.storageType || 'session');
210
+ }
211
+ // Replace default TokenRefreshHandler with one that has callbacks for token refresh
212
+ this.setupTokenRefreshHandler();
213
+ this.logger.debug('AiAgent instance created', { agentId: config.id, endpoint: config.endpoint });
214
+ }
215
+ /**
216
+ * Setup TokenRefreshHandler with callbacks for automatic token refresh
217
+ * Replaces the default handler with one that can actually perform token refresh
218
+ */
219
+ setupTokenRefreshHandler() {
220
+ const handlers = this.messageProcessor.getHandlers();
221
+ const defaultTokenHandler = handlers.find((h) => h instanceof TokenRefreshHandler);
222
+ if (defaultTokenHandler) {
223
+ // Remove default handler
224
+ this.messageProcessor.removeHandler(defaultTokenHandler);
225
+ // Add handler with callbacks
226
+ const tokenRefreshHandler = new TokenRefreshHandler({
227
+ getAccessToken: async () => {
228
+ const token = await this.authService.getToken();
229
+ if (!token) {
230
+ throw new Error('Failed to get access token for refresh');
231
+ }
232
+ return token;
233
+ },
234
+ sendToConnection: async (payload) => {
235
+ if (!this.connection?.isConnected()) {
236
+ this.logger.warn('Cannot send token refresh: connection not available');
237
+ return;
238
+ }
239
+ const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
240
+ await this.connection.send(payloadString);
241
+ },
242
+ });
243
+ // Add at priority 0 (highest priority)
244
+ this.messageProcessor.addHandler(tokenRefreshHandler, 0);
245
+ }
246
+ }
247
+ parseQueryScopes() {
248
+ const raw = this.initParams.scopes?.trim();
249
+ if (!raw)
250
+ return [];
251
+ return raw
252
+ .split(',')
253
+ .map((s) => s.trim())
254
+ .filter((s) => s.length > 0);
255
+ }
256
+ /**
257
+ * Base OAuth resource scopes before platform augmentation: comma-separated `initParams.scopes`
258
+ * overrides `config.scopes` and defaults. Used to seed `addCustomAuthScopes`. For PKCE and hooks
259
+ * after the platform merges scopes, use {@link getAuthScopesForFlow}.
260
+ */
261
+ resolveEffectiveAuthScopes() {
262
+ const fromQuery = this.parseQueryScopes();
263
+ if (fromQuery.length > 0) {
264
+ return [...fromQuery];
265
+ }
266
+ if (this.config.scopes && this.config.scopes.length > 0) {
267
+ return [...this.config.scopes];
268
+ }
269
+ const scopes = ['knowledge.portalmgr.manage', 'core.aiservices.read'];
270
+ if (this.agentDetails?.userType === 'customer') {
271
+ scopes.push('core.customermgr.read');
272
+ }
273
+ return scopes;
274
+ }
275
+ /**
276
+ * Scopes used for PKCE, AuthenticationService.initialize, and HookContract.getAuthScopes.
277
+ * After the platform connector augments scopes into `config.scopes`, that merged list wins.
278
+ */
279
+ getAuthScopesForFlow() {
280
+ if (this.authScopesAugmentedByPlatform && this.config.scopes && this.config.scopes.length > 0) {
281
+ return [...this.config.scopes];
282
+ }
283
+ return this.resolveEffectiveAuthScopes();
284
+ }
285
+ /**
286
+ * Initialize the agent. Must be called after construction and awaited before use.
287
+ *
288
+ * Authenticates (falls back to {@link AnonymousAuthStrategy} if no auth is configured), then:
289
+ *
290
+ * - **Direct flow:** fetches session, creates the WebSocket connection, emits `initialized`.
291
+ * With `autoConnect`, opens the WebSocket automatically.
292
+ * - **Contact Center flow:** runs portal / agent / profile selection over REST only (no WebSocket
293
+ * yet). May emit `portalsAvailable`, `agentsAvailable`, or `profilesAvailable` — call the
294
+ * corresponding `select*` method. Then emits `initialized`. Call {@link AiAgent.connect}
295
+ * afterward (or rely on `autoConnect` after the pipeline completes).
296
+ *
297
+ * @example
298
+ * ```typescript
299
+ * const agent = new AiAgent({ id: 'agent-id', endpoint: 'https://...' });
300
+ * await agent.initialize();
301
+ * // Direct flow: often already connected if autoConnect. CC flow: connect after `initialized`.
302
+ * await agent.connect();
303
+ * ```
304
+ */
305
+ async initialize() {
306
+ if (this.isInitialized) {
307
+ this.logger.debug('Agent already initialized', { agentId: this.config.id });
308
+ return;
309
+ }
310
+ this.logger.debug('Initializing agent', { agentId: this.config.id });
311
+ try {
312
+ this.authScopesAugmentedByPlatform = false;
313
+ // Get deployment info (use cached if getAgentDetails was called first)
314
+ if (!this.deploymentInfo) {
315
+ this.deploymentInfo = await ApiHelper.getDeploymentInfo(this.config.endpoint);
316
+ }
317
+ if (!this.deploymentInfo) {
318
+ const error = new Error('Deployment information not found');
319
+ this.logger.error('Failed to initialize: deployment information not found', error, { agentId: this.config.id });
320
+ throw error;
321
+ }
322
+ // Set up apiHelper if not already set (may have been set by getAgentDetails)
323
+ if (!this.apiHelper) {
324
+ this.apiHelper = new ApiHelper({
325
+ apiDomain: this.deploymentInfo.apiDomain,
326
+ cache: this.config.cache,
327
+ getToken: () => this.authService.getToken(),
328
+ });
329
+ }
330
+ this.logger.debug('Deployment info retrieved', { apiDomain: this.deploymentInfo.apiDomain });
331
+ // Set up token expiring callback to emit tokenExpiring event
332
+ this.authService.setTokenExpiringCallback((expiresAt) => {
333
+ this.logger.debug('Token expiring callback triggered', { expiresAt, agentId: this.config.id });
334
+ this.emit('tokenExpiring', this.createAgentEventResponse('tokenExpiring', {
335
+ reason: 'expiring',
336
+ expiresAt,
337
+ }));
338
+ });
339
+ if (!this.agentDetails) {
340
+ // Initialize auth service with deployment info if not already initialized
341
+ // Default strategy is already anonymous, so we can get token from current strategy
342
+ if (!this.authService.getIsInitialized()) {
343
+ await this.authService.initialize({
344
+ deploymentInfo: this.deploymentInfo,
345
+ // No postAuthentication - we don't want session ID or connection yet
346
+ });
347
+ }
348
+ const accessToken = await this.authService.getToken();
349
+ this.agentDetails = await this.fetchAgentDetails(accessToken);
350
+ }
351
+ // Load platform connector script if applicable (before auth so scopes can be augmented)
352
+ const platform = this.initParams.platform?.toLowerCase();
353
+ const shouldLoadPlatformScript = platform != null &&
354
+ platform !== 'standalone' &&
355
+ platform !== 'test' &&
356
+ /^[a-zA-Z]+$/.test(platform) &&
357
+ this.agentDetails?.agentType === 'contact-center';
358
+ if (shouldLoadPlatformScript) {
359
+ await this.loadAndInitializePlatform();
360
+ }
361
+ // Branch based on whether agent requires authentication
362
+ if (this.agentDetails?.isAuthenticated) {
363
+ const effectiveScopes = this.getAuthScopesForFlow();
364
+ // Agent requires authentication - use PKCE
365
+ // Check if current strategy is anonymous, and switch to PKCE if needed
366
+ // Otherwise keep the same strategy
367
+ if (this.authService.isAnonymousStrategy()) {
368
+ // Build PKCE config from deployment info and agent details
369
+ let pkceConfig;
370
+ // Check if PKCE config was provided in the original auth config
371
+ if (this.config.auth && typeof this.config.auth === 'object' && 'type' in this.config.auth && this.config.auth.type === 'pkce') {
372
+ pkceConfig = this.config.auth.config;
373
+ if (this.parseQueryScopes().length > 0 || this.authScopesAugmentedByPlatform) {
374
+ pkceConfig = { ...pkceConfig, scopes: [...effectiveScopes] };
375
+ }
376
+ }
377
+ else {
378
+ // Build PKCE config from deployment info and agent details
379
+ try {
380
+ const egClientId = this.initParams.egclientid || this.initParams.egclientId || this.initParams.egClientId;
381
+ const localLogin = this.initParams.localLogin === 'true' || undefined;
382
+ pkceConfig = await PKCEAuthStrategy.buildConfigFromDeploymentInfo(this.deploymentInfo, this.agentDetails, this.config.endpoint, effectiveScopes, this.logger.createChild('PKCEAuthStrategy'), this.config.authScheme, egClientId, localLogin);
383
+ }
384
+ catch (error) {
385
+ const err = error instanceof Error ? error : new Error(String(error));
386
+ this.logger.error('Failed to build PKCE config from deployment info', err, { agentId: this.config.id });
387
+ throw new Error(`Failed to build PKCE configuration: ${err.message}`);
388
+ }
389
+ }
390
+ // Switch to PKCE strategy with postAuthentication callback
391
+ // postAuthentication will be called once authentication type is confirmed
392
+ this.logger.debug('Switching from anonymous to PKCE strategy', { agentId: this.config.id });
393
+ await this.authService.switchStrategyTo(pkceConfig, this.onAuthComplete.bind(this));
394
+ }
395
+ else {
396
+ this.logger.debug('Current strategy is not anonymous, keeping existing strategy', {
397
+ agentId: this.config.id,
398
+ currentType: this.authService.getAuthenticationType()
399
+ });
400
+ }
401
+ // Initialize and authenticate - postAuthentication callback will be called after authentication completes
402
+ this.logger.debug('Agent requires authentication, initializing PKCE flow', { agentId: this.config.id });
403
+ await this.authService.initialize({
404
+ deploymentInfo: this.deploymentInfo,
405
+ postAuthentication: this.onAuthComplete,
406
+ scopes: effectiveScopes,
407
+ userType: this.agentDetails?.userType,
408
+ });
409
+ await this.authService.authenticate();
410
+ }
411
+ else {
412
+ // Agent doesn't require authentication - use anonymous strategy
413
+ this.logger.debug('Agent does not require authentication, using anonymous strategy', { agentId: this.config.id });
414
+ // Complete initialization manually (get session, create connection)
415
+ // TODO: POST AUTHENTICATION CALLBACK
416
+ const accessToken = await this.authService.getToken();
417
+ await this.onAuthComplete(accessToken);
418
+ }
419
+ }
420
+ catch (error) {
421
+ // Ensure we don't mark as initialized if initialization failed
422
+ this.isInitialized = false;
423
+ const err = error instanceof Error ? error : new Error(String(error));
424
+ this.logger.error('Failed to initialize agent', err, { agentId: this.config.id });
425
+ throw err;
426
+ }
427
+ }
428
+ /**
429
+ * Build the HookContract by closing over `this`.
430
+ * All getters return live state (not stale snapshots).
431
+ */
432
+ buildHookContract() {
433
+ return {
434
+ getTranscript: () => [...this.callTranscript],
435
+ getInitParams: () => ({ ...this.initParams }),
436
+ getAgentDetails: () => this.agentDetails,
437
+ getMsalAccessToken: () => this.authService.getToken(),
438
+ getDeploymentInfo: () => this.deploymentInfo,
439
+ getPlatformType: () => this.initParams.platform ?? null,
440
+ getEnvironment: () => deriveEnvironment(this.initParams.env),
441
+ getUserId: () => this.initParams.userid ?? this.initParams.userId ?? null,
442
+ getUserContext: () => this.userContext,
443
+ getConversationId: () => this.conversationId,
444
+ getAuthScopes: () => this.getAuthScopesForFlow(),
445
+ getTenantId: () => this.deploymentInfo?.tenantId ?? null,
446
+ getSelectedPortal: () => this.lastSelectedPortal ?? null,
447
+ getCallerInfo: () => this.callerInfo,
448
+ addToTranscript: (entry) => {
449
+ const newEntry = {
450
+ sender: entry.sender,
451
+ content: entry.content,
452
+ timestamp: entry.timestamp ?? new Date(),
453
+ };
454
+ this.callTranscript.push(newEntry);
455
+ this.emit('callTranscriptUpdate', this.createAgentEventResponse('callTranscriptUpdate', {
456
+ entry: newEntry,
457
+ }));
458
+ },
459
+ setCallerInfo: (info) => {
460
+ this.callerInfo = info;
461
+ this.emit('callerInfoUpdate', this.createAgentEventResponse('callerInfoUpdate', {
462
+ callerInfo: info,
463
+ }));
464
+ },
465
+ setPlatformAuthenticated: (v) => { this.isPlatformAuthenticated = v; },
466
+ setPlatformToken: (token) => { this.platformToken = token; },
467
+ setConversationId: (id) => {
468
+ this.conversationId = id;
469
+ this.emit('conversationIdUpdate', this.createAgentEventResponse('conversationIdUpdate', {
470
+ conversationId: id,
471
+ }));
472
+ },
473
+ setUserContext: (ctx) => {
474
+ this.userContext = { ...this.userContext, ...ctx };
475
+ this.emit('userContextUpdate', this.createAgentEventResponse('userContextUpdate', {
476
+ userContext: this.userContext,
477
+ }));
478
+ },
479
+ setUserFilterTags: (tags) => {
480
+ this.filterTags = tags;
481
+ this.emit('filterTagsUpdate', this.createAgentEventResponse('filterTagsUpdate', {
482
+ filterTags: tags,
483
+ }));
484
+ },
485
+ subscribeToAgentWidgetActions: (cb) => {
486
+ const handler = (event) => { cb(event?.type ?? 'unknown', event?.payload); };
487
+ this.on('message', handler);
488
+ return () => { this.off('message', handler); };
489
+ },
490
+ onUserMessage: (msg) => { this.send(msg); },
491
+ onSourceClick: (source) => { this.emit('message', this.createAgentEventResponse('message', { type: 'sourceClick', source })); },
492
+ onIntentConfirm: (intent) => { this.emit('message', this.createAgentEventResponse('message', { type: 'intentConfirm', intent })); },
493
+ };
494
+ }
495
+ /**
496
+ * Load the platform connector script and wire up the HookContract.
497
+ * Called from initialize() when a non-standalone platform is detected.
498
+ */
499
+ async loadAndInitializePlatform() {
500
+ const platform = this.initParams.platform.toLowerCase();
501
+ const environment = deriveEnvironment(this.deploymentInfo?.apiDomain, this.initParams.env);
502
+ this.logger.info('Loading platform connector', { platform, environment });
503
+ await loadPlatformScript({
504
+ platform,
505
+ baseUrl: environment,
506
+ overrideUrl: this.config.platformScriptUrl,
507
+ logger: this.logger,
508
+ });
509
+ this.platformComponentService =
510
+ (typeof window !== 'undefined' ? window : globalThis).PlatformComponentService ?? undefined;
511
+ if (!this.platformComponentService) {
512
+ throw new Error(`Failed to load platform connector script for '${platform}'`);
513
+ }
514
+ this.hookContract = this.buildHookContract();
515
+ if (this.platformComponentService.setHookContract) {
516
+ this.platformComponentService.setHookContract(this.hookContract);
517
+ }
518
+ if (this.platformComponentService.loadCustomHook) {
519
+ this.platformComponentService.loadCustomHook(this.hookContract);
520
+ }
521
+ if (this.platformComponentService.addCustomAuthScopes) {
522
+ const currentScopes = this.resolveEffectiveAuthScopes();
523
+ const augmentedScopes = await this.platformComponentService.addCustomAuthScopes(currentScopes);
524
+ if (augmentedScopes && Array.isArray(augmentedScopes)) {
525
+ this.config.scopes = augmentedScopes;
526
+ this.authScopesAugmentedByPlatform = true;
527
+ }
528
+ }
529
+ this.logger.info('Platform connector initialized', { platform });
530
+ }
531
+ /**
532
+ * Whether to run the portal-based initializer (portals → optional agents → profiles) vs direct session setup.
533
+ * True when portal-related init params or agent configuration indicate a portal-trained / contact-center flow.
534
+ */
535
+ shouldRunPortalInitializationPipeline() {
536
+ const q = this.initParams;
537
+ if ((q.portalIds ?? q.portalids ?? '').trim().length > 0) {
538
+ return true;
539
+ }
540
+ const portals = this.agentDetails?.portals;
541
+ if (Array.isArray(portals) && portals.length > 0) {
542
+ return true;
543
+ }
544
+ // if (this.agentDetails?.agentType === 'contact-center') {
545
+ // return true;
546
+ // }
547
+ if ((q.isDefaultAgent?.toLowerCase() ?? '') === 'true') {
548
+ return true;
549
+ }
550
+ return false;
551
+ }
552
+ /**
553
+ * Session + connection + `initialized` for the non-portal path after auth.
554
+ */
555
+ async runDirectInitializationAfterAuth(accessToken) {
556
+ this.isInitialized = true;
557
+ this.sessionId = await this.getSessionId(accessToken);
558
+ await this.createConnection(this.sessionId);
559
+ const agentDetails = this.agentDetails ?? (this.apiHelper && (await this.fetchAgentDetails(accessToken)));
560
+ const payload = {
561
+ agent: agentDetails ?? { agentId: this.resolvedAgentId, name: 'Unknown' },
562
+ };
563
+ this.emit('initialized', this.createAgentEventResponse('initialized', payload));
564
+ if (this.config.autoConnect) {
565
+ await this.connect();
566
+ }
567
+ }
568
+ async runPortalInitializerPipeline(accessToken) {
569
+ if (this.platformComponentService?.initPlatform) {
570
+ await this.platformComponentService.initPlatform(this.hookContract);
571
+ }
572
+ this.portalInitializer = new PortalInitializer({
573
+ agentId: this.config.id,
574
+ apiHelper: this.apiHelper,
575
+ logger: this.logger.createChild('PortalInitializer'),
576
+ authService: this.authService,
577
+ initParams: this.initParams,
578
+ platformComponentService: this.platformComponentService,
579
+ hookContract: this.hookContract,
580
+ emit: (type, event) => {
581
+ if (type === 'initialized') {
582
+ const payload = event?.payload;
583
+ if (payload?.agent) {
584
+ this.resolvedAgentId = payload.agent.agentId ?? payload.agent.id ?? this.resolvedAgentId;
585
+ }
586
+ this.completedPortalPipeline = true;
587
+ this.lastSelectedPortal = payload?.portal;
588
+ if (payload?.availableProfiles) {
589
+ this.cachedProfiles = payload.availableProfiles;
590
+ const portalId = payload?.portal?.id;
591
+ const profilesCacheKey = this.getPipelineProfilesCacheKey(portalId);
592
+ const existingEntry = this.contextCacheAdapter.get(profilesCacheKey);
593
+ if (!existingEntry) {
594
+ this.contextCacheAdapter.set(profilesCacheKey, {
595
+ value: payload.availableProfiles,
596
+ timestamp: Date.now(),
597
+ });
598
+ }
599
+ }
600
+ this.isInitialized = true;
601
+ }
602
+ this.emit(type, event);
603
+ if (type === 'initialized' && this.config.autoConnect) {
604
+ this.connect();
605
+ }
606
+ },
607
+ createAgentEventResponse: (type, payload) => this.createAgentEventResponse(type, payload),
608
+ isAgentSelectionMode: this.isAgentSelectionMode,
609
+ agentDetails: this.agentDetails,
610
+ pipelineCache: {
611
+ adapter: this.contextCacheAdapter,
612
+ profilesKey: (portalId) => this.getPipelineProfilesCacheKey(portalId),
613
+ ttl: this.config.cache?.ttl ?? 60 * 60 * 1000, // 1 hour
614
+ },
615
+ });
616
+ void this.portalInitializer.start();
617
+ }
618
+ /**
619
+ * Fetch user or customer details after authentication.
620
+ * Determination: if userType is 'customer', fetches customer details; otherwise fetches user details.
621
+ * Best-effort — logs a warning on failure but does not throw.
622
+ */
623
+ async fetchUserOrCustomerDetails(accessToken) {
624
+ if (!this.apiHelper)
625
+ return;
626
+ try {
627
+ const userType = this.agentDetails?.userType || 'user';
628
+ this.userDetails = userType === 'customer'
629
+ ? await this.apiHelper.getCustomerDetails({ authToken: accessToken })
630
+ : await this.apiHelper.getUserDetails({ authToken: accessToken });
631
+ }
632
+ catch (error) {
633
+ this.logger.warn('Failed to fetch user/customer details', {
634
+ error: error instanceof Error ? error : new Error(String(error)),
635
+ agentId: this.config.id,
636
+ });
637
+ this.userDetails = null;
638
+ }
639
+ }
640
+ /**
641
+ * Fetch agent details from API
642
+ * @param accessToken - The access token to use for authentication
643
+ * @returns The agent details
644
+ */
645
+ async fetchAgentDetails(accessToken) {
646
+ if (this.agentDetails) {
647
+ this.backfillAgentDetailsId(this.agentDetails);
648
+ return this.agentDetails;
649
+ }
650
+ accessToken = accessToken ?? await this.authService.getToken() ?? null;
651
+ if (!accessToken) {
652
+ const error = new Error('Access token not found.');
653
+ this.logger.error('Failed to get agent details: access token not found', error, { agentId: this.config.id });
654
+ throw error;
655
+ }
656
+ this.logger.debug('Fetching agent details', { agentId: this.config.id });
657
+ this.agentDetails = await this.apiHelper?.getAiAgentDetails({
658
+ agentId: this.config.id,
659
+ authToken: accessToken,
660
+ });
661
+ this.backfillAgentDetailsId(this.agentDetails);
662
+ this.logger.debug('Agent details retrieved', { agentId: this.config.id });
663
+ return this.agentDetails;
664
+ }
665
+ /**
666
+ * Ensures agent details have agentId from the request context when API omits it.
667
+ *
668
+ * @param details - The agent details to backfill the agentId for
669
+ * @returns The agent details with the agentId backfilled
670
+ */
671
+ backfillAgentDetailsId(details) {
672
+ if (!details)
673
+ return;
674
+ const knownId = this.resolvedAgentId ?? this.config.id;
675
+ if (knownId == null)
676
+ return;
677
+ if (details.agentId == null || details.agentId === '') {
678
+ details.agentId = knownId;
679
+ }
680
+ }
681
+ /**
682
+ * Get the agent details
683
+ * Returns cached agent details if available, otherwise fetches from network.
684
+ * If called before initialize(), performs minimal initialization to fetch agent details only
685
+ * (gets deployment info, fetches anonymous token, fetches agent details - without getting session ID or creating connection).
686
+ * @returns Promise resolving to the agent details
687
+ */
688
+ async getAgentDetails() {
689
+ if (this.agentDetails) {
690
+ return this.agentDetails;
691
+ }
692
+ // If SDK is fully initialized, just fetch with existing token
693
+ if (this.isInitialized) {
694
+ const accessToken = await this.authService.getToken();
695
+ return await this.fetchAgentDetails(accessToken);
696
+ }
697
+ // Minimal initialization: only what's needed for agent details
698
+ this.logger.debug('Performing minimal initialization to fetch agent details', { agentId: this.config.id });
699
+ // 1. Get deployment info (uses cache if available)
700
+ const deploymentInfo = await this.getDeploymentInfo();
701
+ if (!deploymentInfo) {
702
+ const error = new Error('Deployment information not found');
703
+ this.logger.error('Failed to get agent details: deployment info not found', error, { agentId: this.config.id });
704
+ throw error;
705
+ }
706
+ // 2. Ensure apiHelper is set up
707
+ if (!this.apiHelper) {
708
+ this.apiHelper = new ApiHelper({
709
+ apiDomain: deploymentInfo.apiDomain,
710
+ cache: this.config.cache,
711
+ getToken: () => this.authService.getToken(),
712
+ });
713
+ }
714
+ // 3. Get token from the main strategy (defaults to anonymous if no auth config provided)
715
+ // Initialize auth service if not already initialized
716
+ if (!this.authService.getIsInitialized()) {
717
+ await this.authService.initialize({
718
+ deploymentInfo: deploymentInfo,
719
+ // No postAuthentication - we don't want session ID or connection yet
720
+ });
721
+ }
722
+ const accessToken = await this.authService.getToken();
723
+ return await this.fetchAgentDetails(accessToken);
724
+ }
725
+ /**
726
+ * Get the deployment information
727
+ * Returns cached deployment info if available, otherwise fetches from network.
728
+ * Does not require initialization - only needs the endpoint URL.
729
+ * @returns Promise resolving to the deployment information
730
+ */
731
+ async getDeploymentInfo() {
732
+ if (this.deploymentInfo) {
733
+ return this.deploymentInfo;
734
+ }
735
+ // Fetch from network if not cached
736
+ this.logger.debug('Fetching deployment info from network', { endpoint: this.config.endpoint });
737
+ this.deploymentInfo = await ApiHelper.getDeploymentInfo(this.config.endpoint);
738
+ return this.deploymentInfo;
739
+ }
740
+ /**
741
+ * Get the agent name from cached agent details
742
+ * @returns The agent name
743
+ */
744
+ async getAgentName() {
745
+ if (!this.agentDetails) {
746
+ throw new Error('Agent details not found. Call initialize() first.');
747
+ }
748
+ return this.agentDetails?.name ?? this.agentDetails?.agentProfileDetails?.name ?? '';
749
+ }
750
+ /**
751
+ * Get session ID
752
+ * @param accessToken - The access token to use for authentication
753
+ * @returns The session ID
754
+ */
755
+ async getSessionId(accessToken) {
756
+ // If sessionId was provided in config, return it (skip network fetch)
757
+ if (this.sessionId !== undefined) {
758
+ this.logger.debug('Using sessionId from config, skipping network fetch', {
759
+ sessionId: this.sessionId,
760
+ agentId: this.resolvedAgentId,
761
+ });
762
+ return this.sessionId;
763
+ }
764
+ // Otherwise, fetch from network (uses resolvedAgentId for chat identity)
765
+ return await this.apiHelper?.getAiAgentSession({
766
+ agentId: this.resolvedAgentId,
767
+ authToken: accessToken ?? await this.authService.getToken() ?? null,
768
+ });
769
+ }
770
+ /**
771
+ * Get WebSocket endpoint
772
+ * @param sessionId - The session ID
773
+ * @returns The WebSocket endpoint
774
+ */
775
+ getWsEndpoint(sessionId) {
776
+ let websocketUrl = this.deploymentInfo?.aiAgentDomain;
777
+ websocketUrl = websocketUrl.indexOf("http") !== 0 ? "https://" + websocketUrl : websocketUrl;
778
+ try {
779
+ const parsedUrl = new URL(websocketUrl);
780
+ parsedUrl.hostname = `chat.${parsedUrl.hostname}`;
781
+ websocketUrl = parsedUrl.toString();
782
+ websocketUrl = `${websocketUrl}?sessionId=${sessionId}`;
783
+ this.logger.debug('WebSocket endpoint constructed', { endpoint: websocketUrl, sessionId });
784
+ }
785
+ catch (error) {
786
+ const err = new Error('Failed to get WebSocket endpoint');
787
+ this.logger.error('Failed to construct WebSocket endpoint', err, { sessionId });
788
+ throw err;
789
+ }
790
+ return websocketUrl;
791
+ }
792
+ /**
793
+ * Create the connection after authentication is complete
794
+ * This is called by the postAuthentication callback
795
+ */
796
+ async createConnection(sessionId) {
797
+ if (this.connection) {
798
+ this.logger.debug('Connection already exists, skipping creation', { sessionId });
799
+ return; // Connection already exists
800
+ }
801
+ const wsEndpoint = this.getWsEndpoint(sessionId);
802
+ this.logger.info('Creating connection', { endpoint: wsEndpoint, sessionId });
803
+ // Create connection
804
+ this.connection = new Connection({
805
+ endpoint: wsEndpoint,
806
+ maxReconnectAttempts: this.config.maxReconnectAttempts,
807
+ baseReconnectDelay: this.config.baseReconnectDelay,
808
+ maxReconnectDelay: this.config.maxReconnectDelay,
809
+ logger: this.logger.createChild('Connection'),
810
+ });
811
+ // Forward connection events
812
+ this.setupConnectionEvents();
813
+ }
814
+ /**
815
+ * Whether the agent has completed initialization.
816
+ * Becomes `true` after the init pipeline completes (e.g. after the `initialized` event).
817
+ *
818
+ * @returns `true` if initialized, `false` otherwise
819
+ *
820
+ * @example
821
+ * ```typescript
822
+ * if (agent.getIsInitialized()) {
823
+ * await agent.connect();
824
+ * } else {
825
+ * agent.once('initialized', () => agent.connect());
826
+ * }
827
+ * ```
828
+ */
829
+ getIsInitialized() {
830
+ return this.isInitialized;
831
+ }
832
+ /**
833
+ * Get current connection state.
834
+ *
835
+ * @returns The current connection state
836
+ * @throws Error if agent is not initialized
837
+ *
838
+ * @example
839
+ * ```typescript
840
+ * const state = agent.getState();
841
+ * if (state === ConnectionState.CONNECTED) {
842
+ * console.log("Ready to send messages");
843
+ * }
844
+ * ```
845
+ *
846
+ * @see {@link ConnectionState} for available states
847
+ */
848
+ getState() {
849
+ if (!this.connection) {
850
+ const error = new Error('Connection not initialized. Call initialize() first.');
851
+ this.logger.error('Failed to get connection state', error, { agentId: this.resolvedAgentId });
852
+ throw error;
853
+ }
854
+ return this.connection.getState();
855
+ }
856
+ /**
857
+ * Check if the agent is currently connected.
858
+ *
859
+ * @returns `true` if connected, `false` otherwise
860
+ *
861
+ * @example
862
+ * ```typescript
863
+ * if (agent.isConnected()) {
864
+ * await agent.send("Hello!");
865
+ * } else {
866
+ * console.log("Waiting for connection...");
867
+ * }
868
+ * ```
869
+ */
870
+ isConnected() {
871
+ return this.connection?.isConnected() ?? false;
872
+ }
873
+ /**
874
+ * Connect to the agent endpoint.
875
+ *
876
+ * Establishes a WebSocket connection to the AI Agent server. Must call {@link initialize} first.
877
+ *
878
+ * For Contact Center agents, the connection object is created lazily on this call (session fetch
879
+ * uses the resolved agent ID, including Flow B after agent selection).
880
+ *
881
+ * @throws Error if agent is not initialized
882
+ *
883
+ * @example
884
+ * ```typescript
885
+ * await agent.initialize();
886
+ * await agent.connect();
887
+ * console.log("Connected!");
888
+ * ```
889
+ */
890
+ async connect() {
891
+ this.logger.debug('connect() called', {
892
+ agentId: this.resolvedAgentId,
893
+ instanceId: this._instanceId,
894
+ isInitialized: this.isInitialized,
895
+ hasConnection: !!this.connection,
896
+ });
897
+ if (!this.isInitialized) {
898
+ const error = new Error('Agent not initialized. Call initialize() first.');
899
+ this.logger.error('Failed to connect: agent not initialized', error, {
900
+ agentId: this.resolvedAgentId,
901
+ isInitialized: this.isInitialized,
902
+ });
903
+ throw error;
904
+ }
905
+ // Lazy connection creation: if no connection exists (CC flow or PKCE redirect),
906
+ // get token, fetch session, create connection
907
+ if (!this.connection) {
908
+ const accessToken = await this.authService.getToken();
909
+ if (!accessToken) {
910
+ const error = new Error('Connection not initialized. Ensure initialize() completed successfully and authentication completed.');
911
+ this.logger.error('Failed to connect: no access token', error, {
912
+ agentId: this.resolvedAgentId,
913
+ isInitialized: this.isInitialized,
914
+ hasConnection: false,
915
+ });
916
+ throw error;
917
+ }
918
+ this.logger.debug('Lazily creating connection', { agentId: this.resolvedAgentId });
919
+ this.sessionId = await this.getSessionId(accessToken);
920
+ await this.createConnection(this.sessionId);
921
+ }
922
+ this.logger.info('Connecting to agent', { agentId: this.resolvedAgentId });
923
+ await this.connection.connect();
924
+ }
925
+ /**
926
+ * Disconnect from the agent endpoint.
927
+ *
928
+ * By default, sends a graceful disconnect message before closing the connection.
929
+ * Use `skipGracefulDisconnect: true` for immediate disconnection.
930
+ *
931
+ * @param options - Disconnect options
932
+ * @param options.skipGracefulDisconnect - If true, skip sending graceful disconnect message
933
+ *
934
+ * @example Graceful disconnect
935
+ * ```typescript
936
+ * await agent.disconnect();
937
+ * ```
938
+ *
939
+ * @example Immediate disconnect
940
+ * ```typescript
941
+ * await agent.disconnect({ skipGracefulDisconnect: true });
942
+ * ```
943
+ */
944
+ async disconnect(options) {
945
+ if (!this.connection) {
946
+ this.logger.debug('Connection not initialized, skipping disconnect', { agentId: this.resolvedAgentId });
947
+ return; // Already disconnected or not initialized
948
+ }
949
+ // Send graceful disconnect message if connected and not skipped
950
+ if (this.connection.isConnected() && !options?.skipGracefulDisconnect) {
951
+ try {
952
+ this.logger.debug('Sending graceful disconnect message', { agentId: this.resolvedAgentId });
953
+ const gracefulDisconnectMessage = createGracefulDisconnectMessage();
954
+ await this.send(gracefulDisconnectMessage);
955
+ // Give a small delay to ensure the message is sent before disconnecting
956
+ await this.delay(100);
957
+ }
958
+ catch (error) {
959
+ // Log error but continue with disconnect
960
+ const err = error instanceof Error ? error : new Error(String(error));
961
+ this.logger.warn('Failed to send graceful disconnect message, continuing with disconnect', {
962
+ error: err,
963
+ agentId: this.resolvedAgentId
964
+ });
965
+ }
966
+ }
967
+ this.logger.info('Disconnecting from agent', { agentId: this.resolvedAgentId });
968
+ this.connection.disconnect();
969
+ }
970
+ /**
971
+ * Select a portal (CC flow). Call when portalsAvailable event is emitted and user has chosen.
972
+ *
973
+ * @param portal - The selected portal
974
+ * @throws Error if portal initializer is not active
975
+ *
976
+ * @example
977
+ * ```typescript
978
+ * agent.on('portalsAvailable', (e) => {
979
+ * const portal = showPortalPicker(e.payload.portals);
980
+ * agent.selectPortal(portal);
981
+ * });
982
+ * ```
983
+ */
984
+ selectPortal(portal) {
985
+ if (!this.portalInitializer) {
986
+ throw new Error('selectPortal can only be called during portal initialization flow');
987
+ }
988
+ this.portalInitializer.onPortalSelected(portal);
989
+ }
990
+ /**
991
+ * Select an agent (Flow B only — `initParams.isDefaultAgent === "true"`).
992
+ * Call when `agentsAvailable` is emitted and the user has chosen.
993
+ *
994
+ * @param agent - The selected agent
995
+ * @throws Error if portal initializer is not active
996
+ *
997
+ * @example
998
+ * ```typescript
999
+ * agent.on('agentsAvailable', (e) => {
1000
+ * const selected = showAgentPicker(e.payload.agents);
1001
+ * agent.selectAgent(selected);
1002
+ * });
1003
+ * ```
1004
+ */
1005
+ selectAgent(agent) {
1006
+ if (!this.portalInitializer) {
1007
+ throw new Error('selectAgent can only be called during portal initialization flow');
1008
+ }
1009
+ this.portalInitializer.onAgentSelected(agent);
1010
+ }
1011
+ /**
1012
+ * Select a user profile (CC flow). Call when profilesAvailable event is emitted and user has chosen.
1013
+ *
1014
+ * @param profile - The selected profile
1015
+ * @throws Error if portal initializer is not active
1016
+ *
1017
+ * @example
1018
+ * ```typescript
1019
+ * agent.on('profilesAvailable', (e) => {
1020
+ * const profile = showProfilePicker(e.payload.profiles);
1021
+ * agent.selectUserProfile(profile);
1022
+ * });
1023
+ * ```
1024
+ */
1025
+ selectUserProfile(profile) {
1026
+ if (!this.portalInitializer) {
1027
+ throw new Error('selectUserProfile can only be called during portal initialization flow');
1028
+ }
1029
+ this.portalInitializer.onProfileSelected(profile);
1030
+ }
1031
+ /**
1032
+ * Get the stored initialization parameters from config.
1033
+ *
1034
+ * @returns The init params object (empty object if none provided)
1035
+ *
1036
+ * @example
1037
+ * ```typescript
1038
+ * const initParams = agent.getInitParams();
1039
+ * const userId = initParams.userid;
1040
+ * ```
1041
+ */
1042
+ getInitParams() {
1043
+ return { ...this.initParams };
1044
+ }
1045
+ /**
1046
+ * Restart the CC widget initialization pipeline from scratch.
1047
+ *
1048
+ * This method tears down the current initialization state and re-runs the
1049
+ * full pipeline (portal selection → agent selection → profile selection),
1050
+ * allowing the consumer to make new selections. After completion, the
1051
+ * `initialized` event fires again and the consumer should call `connect()`.
1052
+ *
1053
+ * **What it does:**
1054
+ * 1. Checks `completedPortalPipeline`: if false (non-CC agent), delegates to `restartConnection()` and returns
1055
+ * 2. Destroys the current `PortalInitializer` instance (rejects any pending gating promises)
1056
+ * 3. Disconnects the current WebSocket connection (if any) and clears session, queue, transcript
1057
+ * 4. Resets `resolvedAgentId` to `config.id` and `isInitialized` to false
1058
+ * 5. Re-obtains an auth token and calls `onAuthComplete` to restart the pipeline (or direct flow)
1059
+ *
1060
+ * **Important:** For agents that completed the CC initialization pipeline
1061
+ * (portal → agent → profile selection), this re-runs the full pipeline.
1062
+ * For agents that did not complete it (e.g. direct flow from the start, or
1063
+ * contact-center agents that fell back to direct flow because they have no
1064
+ * portals), this method delegates to `restartConnection()` so the consumer
1065
+ * can call it for any restart without branching. The consumer must
1066
+ * re-register or still have active event listeners for `portalsAvailable`,
1067
+ * `agentsAvailable`, `profilesAvailable`, and `initialized` before calling
1068
+ * this method (CC pipeline path only).
1069
+ *
1070
+ * @throws Error if authentication token cannot be obtained (CC path) or for restart (direct path)
1071
+ *
1072
+ * @example
1073
+ * ```typescript
1074
+ * // User wants to switch portals — restart the pipeline
1075
+ * agent.on('portalsAvailable', (e) => {
1076
+ * showPortalPicker(e.payload.portals, (p) => agent.selectPortal(p));
1077
+ * });
1078
+ *
1079
+ * agent.on('initialized', async () => {
1080
+ * await agent.connect();
1081
+ * });
1082
+ *
1083
+ * await agent.restartPortalInitializer();
1084
+ * ```
1085
+ */
1086
+ async restartPortalInitializer() {
1087
+ if (!this.completedPortalPipeline) {
1088
+ this.logger.debug('restartPortalInitializer: agent did not complete portal pipeline, delegating to restartConnection', {
1089
+ agentId: this.config.id,
1090
+ });
1091
+ await this.restartConnection();
1092
+ return;
1093
+ }
1094
+ if (this.portalInitializer) {
1095
+ this.portalInitializer.destroy();
1096
+ this.portalInitializer = undefined;
1097
+ }
1098
+ // Clear profile cache so the restarted pipeline fetches fresh data
1099
+ if (this.lastSelectedPortal?.id != null) {
1100
+ this.contextCacheAdapter.delete(this.getPipelineProfilesCacheKey(this.lastSelectedPortal.id));
1101
+ }
1102
+ if (this.connection) {
1103
+ await this.disconnect();
1104
+ }
1105
+ this.connection = undefined;
1106
+ this.sessionId = undefined;
1107
+ this.clearQueue();
1108
+ this.clearTranscript();
1109
+ if (this.initParams.platform === "test") {
1110
+ this.clearCallTranscript();
1111
+ }
1112
+ this.resolvedAgentId = this.config.id;
1113
+ this.isInitialized = false;
1114
+ const accessToken = await this.authService.getToken();
1115
+ if (!accessToken) {
1116
+ const error = new Error('Failed to get access token for restart');
1117
+ this.logger.error('restartPortalInitializer: no access token', error, { agentId: this.config.id });
1118
+ throw error;
1119
+ }
1120
+ await this.onAuthComplete(accessToken);
1121
+ }
1122
+ /**
1123
+ * @deprecated Use {@link restartPortalInitializer} instead.
1124
+ */
1125
+ async restartCcWidgetInitializer() {
1126
+ return this.restartPortalInitializer();
1127
+ }
1128
+ /**
1129
+ * Update the active user profile after initialization.
1130
+ *
1131
+ * Use this when the consumer wants to switch profiles without re-running the
1132
+ * full portal/agent selection pipeline. This method:
1133
+ * 1. Persists the new profile selection via the `selectUserProfile` API
1134
+ * (if the profile is not already the last-used profile)
1135
+ * 2. Disconnects the current WebSocket connection (if any)
1136
+ * 3. Clears the message queue and transcript
1137
+ * 4. Fetches a new session ID and reconnects
1138
+ * 5. Emits `initialized` with the updated profile in the payload
1139
+ *
1140
+ * This is the equivalent of the cc-widget's profile dropdown behavior:
1141
+ * change the profile → restart session → re-send user context.
1142
+ *
1143
+ * **Important:** This method is only valid after the CC initialization
1144
+ * pipeline has completed. Calling it on a non-CC agent or before
1145
+ * initialization throws an error.
1146
+ *
1147
+ * @param profile - The new user profile to activate
1148
+ * @throws Error if agent is not initialized
1149
+ * @throws Error if agent did not go through the CC initialization flow
1150
+ * @throws Error if no portal is currently selected
1151
+ *
1152
+ * @example
1153
+ * ```typescript
1154
+ * // User picks a different profile from a dropdown
1155
+ * const profiles = cachedProfiles; // from the profilesAvailable event
1156
+ * agent.on("initialized", async () => {
1157
+ * await agent.connect();
1158
+ * });
1159
+ * await agent.updateUserProfile(profiles[2]);
1160
+ * ```
1161
+ */
1162
+ async updateUserProfile(profile) {
1163
+ if (!this.isInitialized) {
1164
+ const error = new Error('updateUserProfile can only be called after initialization');
1165
+ this.logger.error('updateUserProfile: agent not initialized', error, { agentId: this.config.id });
1166
+ throw error;
1167
+ }
1168
+ if (!this.completedPortalPipeline) {
1169
+ const error = new Error('updateUserProfile can only be called on agents that used the portal initialization flow');
1170
+ this.logger.error('updateUserProfile: not a portal-configured agent', error, { agentId: this.config.id });
1171
+ throw error;
1172
+ }
1173
+ if (!this.lastSelectedPortal) {
1174
+ const error = new Error('updateUserProfile requires a selected portal');
1175
+ this.logger.error('updateUserProfile: no portal selected', error, { agentId: this.config.id });
1176
+ throw error;
1177
+ }
1178
+ if (profile.id !== 'none' && !profile.isLastUsedInPortal && this.apiHelper) {
1179
+ try {
1180
+ await this.apiHelper.selectUserProfile({
1181
+ portalId: this.lastSelectedPortal.id,
1182
+ profileId: profile.id,
1183
+ });
1184
+ }
1185
+ catch (err) {
1186
+ this.logger.warn('Failed to persist profile selection in updateUserProfile', {
1187
+ error: err instanceof Error ? err : new Error(String(err)),
1188
+ agentId: this.config.id,
1189
+ });
1190
+ }
1191
+ }
1192
+ await this.restartConnection();
1193
+ const payload = {
1194
+ portal: this.lastSelectedPortal,
1195
+ profile,
1196
+ availableProfiles: this.cachedProfiles,
1197
+ agent: this.agentDetails ?? { agentId: this.resolvedAgentId, name: 'Unknown' },
1198
+ };
1199
+ this.emit('initialized', this.createAgentEventResponse('initialized', payload));
1200
+ }
1201
+ /**
1202
+ * Restart the connection with a fresh session.
1203
+ *
1204
+ * This method:
1205
+ * 1. Gracefully disconnects from the current session
1206
+ * 2. Clears all queued messages and transcript
1207
+ * 3. Obtains a new session ID (or uses provided one)
1208
+ * 4. Reconnects to the new session
1209
+ * 5. Sends any stored context to the new session
1210
+ *
1211
+ * **Note:** All queued messages will be lost during restart.
1212
+ *
1213
+ * @param options - Optional restart options
1214
+ * @param options.sessionId - Optional session ID to use for restart. If provided, skips fetching from network.
1215
+ * @throws Error if agent is not initialized
1216
+ *
1217
+ * @example
1218
+ * ```typescript
1219
+ * // Start a fresh conversation (fetches new sessionId)
1220
+ * await agent.restartConnection();
1221
+ *
1222
+ * // Restart with a specific sessionId
1223
+ * await agent.restartConnection({ sessionId: 'existing-session-id' });
1224
+ *
1225
+ * // Context is automatically restored
1226
+ * await agent.send("Hello again!");
1227
+ * ```
1228
+ */
1229
+ async restartConnection(options) {
1230
+ // Validate that agent is initialized
1231
+ if (!this.isInitialized) {
1232
+ const error = new Error('Agent not initialized. Call initialize() first.');
1233
+ this.logger.error('Failed to restart connection: agent not initialized', error, { agentId: this.resolvedAgentId });
1234
+ throw error;
1235
+ }
1236
+ if (!this.apiHelper) {
1237
+ const error = new Error('API helper not initialized. Call initialize() first.');
1238
+ this.logger.error('Failed to restart connection: API helper not initialized', error, { agentId: this.resolvedAgentId });
1239
+ throw error;
1240
+ }
1241
+ try {
1242
+ this.logger.info('Restarting connection', { agentId: this.resolvedAgentId, sessionId: this.sessionId });
1243
+ // Step 1: Gracefully disconnect from current session
1244
+ if (this.connection) {
1245
+ await this.disconnect();
1246
+ }
1247
+ // Step 2: Clear all queued messages and transcript - previous messages will be lost
1248
+ this.clearQueue();
1249
+ this.clearTranscript();
1250
+ this.logger.debug('Message queue and transcript cleared', { agentId: this.resolvedAgentId });
1251
+ // Step 3: Get new sessionId
1252
+ let newSessionId;
1253
+ if (options?.sessionId !== undefined) {
1254
+ // Use provided sessionId
1255
+ newSessionId = options.sessionId;
1256
+ this.logger.debug('Using provided sessionId for restart', {
1257
+ agentId: this.resolvedAgentId,
1258
+ providedSessionId: newSessionId
1259
+ });
1260
+ }
1261
+ else {
1262
+ // Fetch new sessionId from API (ignore config sessionId for restart)
1263
+ const accessToken = await this.authService.getToken();
1264
+ if (!accessToken) {
1265
+ const error = new Error('Failed to get access token for restart');
1266
+ this.logger.error('Failed to restart connection: access token not available', error, { agentId: this.resolvedAgentId });
1267
+ throw error;
1268
+ }
1269
+ newSessionId = await this.apiHelper?.getAiAgentSession({
1270
+ agentId: this.resolvedAgentId,
1271
+ authToken: accessToken,
1272
+ });
1273
+ if (!newSessionId) {
1274
+ const error = new Error('Failed to get new sessionId for restart');
1275
+ this.logger.error('Failed to restart connection: new sessionId not obtained', error, { agentId: this.resolvedAgentId });
1276
+ throw error;
1277
+ }
1278
+ }
1279
+ this.logger.debug('New sessionId obtained', {
1280
+ agentId: this.resolvedAgentId,
1281
+ oldSessionId: this.sessionId,
1282
+ newSessionId
1283
+ });
1284
+ // Step 5: Clean up existing connection
1285
+ // Set connection to undefined to allow createConnection to create a new one
1286
+ this.connection = undefined;
1287
+ // Step 6: Update sessionId
1288
+ this.sessionId = newSessionId;
1289
+ // Step 7: Create new connection with new sessionId
1290
+ await this.createConnection(String(newSessionId));
1291
+ // Step 8: Connect to the new session
1292
+ await this.connect();
1293
+ // Step 9: Send stored context immediately after reconnection
1294
+ await this.sendStoredContext();
1295
+ this.logger.info('Connection restarted successfully', {
1296
+ agentId: this.resolvedAgentId,
1297
+ newSessionId
1298
+ });
1299
+ }
1300
+ catch (error) {
1301
+ const err = error instanceof Error ? error : new Error(String(error));
1302
+ this.logger.error('Failed to restart connection', err, { agentId: this.resolvedAgentId });
1303
+ this.emit('error', this.createAgentEventResponse('error', {
1304
+ error: err,
1305
+ }));
1306
+ throw err;
1307
+ }
1308
+ }
1309
+ /**
1310
+ * Normalize input data to a Message instance
1311
+ * @private
1312
+ */
1313
+ normalizeToMessage(data, options) {
1314
+ // Already a Message instance
1315
+ if (data instanceof Message) {
1316
+ // Update from/to if provided in options
1317
+ if (options?.from !== undefined || options?.to !== undefined) {
1318
+ return data.clone({
1319
+ from: options?.from ?? data.from,
1320
+ to: options?.to ?? data.to,
1321
+ });
1322
+ }
1323
+ return data;
1324
+ }
1325
+ else if (typeof data === 'string') {
1326
+ // String input - create customer message
1327
+ return new Message(PERSONA.CUSTOMER, ROLE.HUMAN, data, {
1328
+ messageId: options?.id,
1329
+ from: options?.from,
1330
+ to: options?.to,
1331
+ });
1332
+ }
1333
+ else if (data && typeof data === 'object') {
1334
+ // Object input - extract message properties
1335
+ return new Message(data.persona || PERSONA.CUSTOMER, data.role || ROLE.HUMAN, data.content, {
1336
+ messageId: options?.id || data.messageId,
1337
+ messageData: data.messageData,
1338
+ from: options?.from || data.from,
1339
+ to: options?.to || data.to,
1340
+ });
1341
+ }
1342
+ else {
1343
+ throw new MessageError('Invalid message data: must be a Message instance, string, or object');
1344
+ }
1345
+ }
1346
+ /**
1347
+ * Send a message to the agent
1348
+ * Messages are queued if offline and automatically sent when connected
1349
+ * Context messages are automatically stored in cache for use on reconnection
1350
+ * @param data - Message data (can be a Message instance, plain object, or string)
1351
+ * @param options - Optional message options
1352
+ * @param options.id - Optional message ID
1353
+ * @param options.from - Optional sender identifier (agent ID, customer ID, etc.)
1354
+ * @param options.to - Optional recipient identifier (agent ID, etc.)
1355
+ * @returns Message ID
1356
+ */
1357
+ async send(data, options) {
1358
+ // Check if this is a context message and store the context before normalizing
1359
+ if (this.isContextMessage(data)) {
1360
+ const context = this.extractContextFromMessage(data);
1361
+ if (context) {
1362
+ this.storeContext(context);
1363
+ }
1364
+ }
1365
+ let message = this.normalizeToMessage(data, options);
1366
+ // Assign default from/to values before validation
1367
+ if (!message.from || !message.to) {
1368
+ message = message.clone({
1369
+ from: message.from || 'customer',
1370
+ to: message.to || await this.getAgentName(),
1371
+ });
1372
+ }
1373
+ // Validate message
1374
+ message.validate();
1375
+ // Serialize message for transmission
1376
+ const payload = message.toPayloadString();
1377
+ this.logger.debug('Sending message', {
1378
+ messageId: message.messageId,
1379
+ from: message.from,
1380
+ to: message.to,
1381
+ payload
1382
+ });
1383
+ const messageId = message.messageId || options?.id || this.generateMessageId();
1384
+ // Store message in transcript before sending
1385
+ this.transcript.add(message, 'sent', this.sessionId, this.resolvedAgentId);
1386
+ // Emit transcript update event
1387
+ this.emit('transcriptUpdate', {
1388
+ type: 'transcriptUpdate',
1389
+ timestamp: Date.now(),
1390
+ sessionId: this.sessionId,
1391
+ agentId: this.resolvedAgentId,
1392
+ payload: {
1393
+ entry: {
1394
+ message,
1395
+ direction: 'sent',
1396
+ timestamp: message.timestamp || Date.now(),
1397
+ sessionId: this.sessionId,
1398
+ agentId: this.resolvedAgentId,
1399
+ },
1400
+ },
1401
+ });
1402
+ // If connection exists and is connected, try to send immediately
1403
+ if (this.connection?.isConnected()) {
1404
+ try {
1405
+ await this.connection.send(payload);
1406
+ this.logger.debug('Message sent successfully', { messageId });
1407
+ return messageId;
1408
+ }
1409
+ catch (error) {
1410
+ // If send fails, queue the message
1411
+ if (error instanceof ConnectionError) {
1412
+ this.logger.warn('Failed to send message, queueing', { messageId, error: error.message });
1413
+ return this.queueMessage(JSON.parse(payload), messageId);
1414
+ }
1415
+ this.logger.error('Failed to send message', error instanceof Error ? error : new Error(String(error)), { messageId });
1416
+ throw error;
1417
+ }
1418
+ }
1419
+ // Queue the message if not connected or connection not initialized
1420
+ this.logger.debug('Connection not available, queueing message', { messageId, queueSize: this.messageQueue.size() });
1421
+ return this.queueMessage(payload, messageId);
1422
+ }
1423
+ /**
1424
+ * Get the current queue size
1425
+ */
1426
+ getQueueSize() {
1427
+ return this.messageQueue.size();
1428
+ }
1429
+ /**
1430
+ * Clear the message queue
1431
+ */
1432
+ clearQueue() {
1433
+ this.messageQueue.clear();
1434
+ }
1435
+ /**
1436
+ * Get transcript entries
1437
+ * @param options - Optional filtering options
1438
+ * @returns Array of transcript entries with Message objects
1439
+ */
1440
+ getTranscript(options) {
1441
+ return this.transcript.getEntries(options);
1442
+ }
1443
+ /**
1444
+ * Get transcript entries as plain objects (JSON-serializable)
1445
+ * @param options - Optional filtering options
1446
+ * @returns Array of plain objects representing transcript entries
1447
+ */
1448
+ getTranscriptAsJSON(options) {
1449
+ return this.transcript.getEntriesAsJSON(options);
1450
+ }
1451
+ /**
1452
+ * Get the number of entries in the transcript
1453
+ * @returns Number of transcript entries
1454
+ */
1455
+ getTranscriptSize() {
1456
+ return this.transcript.size();
1457
+ }
1458
+ /**
1459
+ * Clear all transcript entries
1460
+ */
1461
+ clearTranscript() {
1462
+ this.transcript.clear();
1463
+ }
1464
+ /**
1465
+ * Get the call transcript — the live customer-agent conversation from the
1466
+ * telephony platform (Genesys, Amazon Connect, etc.), pushed by the platform
1467
+ * connector via the HookContract.
1468
+ *
1469
+ * This is distinct from {@link getTranscript}, which returns the AI Agent
1470
+ * chat transcript (WebSocket messages).
1471
+ *
1472
+ * @returns A shallow copy of the call transcript entries
1473
+ */
1474
+ getCallTranscript() {
1475
+ return [...this.callTranscript];
1476
+ }
1477
+ /**
1478
+ * Get the caller information set by the platform connector via
1479
+ * HookContract.setCallerInfo(). Available after the platform connector
1480
+ * has initialized (typically before the `initialized` event fires).
1481
+ *
1482
+ * @returns The caller info object, or null if not yet set
1483
+ */
1484
+ getCallerInfo() {
1485
+ return this.callerInfo;
1486
+ }
1487
+ /**
1488
+ * Returns the authenticated user's or customer's details fetched after authentication.
1489
+ * Available after the `initialized` event fires. Returns null if details could not be fetched.
1490
+ */
1491
+ getUserDetails() {
1492
+ return this.userDetails;
1493
+ }
1494
+ getConversationId() {
1495
+ return this.conversationId;
1496
+ }
1497
+ /**
1498
+ * Clear all call transcript entries.
1499
+ */
1500
+ clearCallTranscript() {
1501
+ this.callTranscript = [];
1502
+ }
1503
+ /**
1504
+ * Get the context cache key for this agent
1505
+ * @returns The cache key for storing context
1506
+ */
1507
+ getContextCacheKey() {
1508
+ return `${CONTEXT_CACHE_KEY_PREFIX}${this.resolvedAgentId}`;
1509
+ }
1510
+ /**
1511
+ * Cache key for persisted profile list (pipeline restart).
1512
+ * Includes portalId so switching portals doesn't serve stale profiles.
1513
+ */
1514
+ getPipelineProfilesCacheKey(portalId) {
1515
+ const suffix = portalId != null
1516
+ ? `${this.resolvedAgentId}_${portalId}`
1517
+ : this.resolvedAgentId;
1518
+ return `${PIPELINE_PROFILES_CACHE_KEY_PREFIX}${suffix}`;
1519
+ }
1520
+ /**
1521
+ * Check if a message is a context message
1522
+ * @param data - The message data to check
1523
+ * @returns True if this is a context message
1524
+ */
1525
+ isContextMessage(data) {
1526
+ if (data instanceof Message) {
1527
+ return data.persona === PERSONA.SYSTEM && data.role === ROLE.CONTEXT;
1528
+ }
1529
+ if (data && typeof data === 'object') {
1530
+ return data.persona === PERSONA.SYSTEM && data.role === ROLE.CONTEXT;
1531
+ }
1532
+ return false;
1533
+ }
1534
+ /**
1535
+ * Extract context data from a context message
1536
+ * @param data - The message data to extract context from
1537
+ * @returns The context object or null if not a context message
1538
+ */
1539
+ extractContextFromMessage(data) {
1540
+ if (data instanceof Message) {
1541
+ return data.messageData?.context || null;
1542
+ }
1543
+ if (data && typeof data === 'object') {
1544
+ return data.messageData?.context || null;
1545
+ }
1546
+ return null;
1547
+ }
1548
+ /**
1549
+ * Store context in cache
1550
+ * @param context - The context object to store
1551
+ */
1552
+ storeContext(context) {
1553
+ const cacheKey = this.getContextCacheKey();
1554
+ this.contextCacheAdapter.set(cacheKey, {
1555
+ value: context,
1556
+ timestamp: Date.now(),
1557
+ });
1558
+ this.logger.debug('Context stored in cache', { agentId: this.resolvedAgentId, context });
1559
+ }
1560
+ /**
1561
+ * Get the stored context for this agent
1562
+ * Returns the context object that was previously sent via a context message
1563
+ * @returns The stored context object or null if no context is stored
1564
+ */
1565
+ getContext() {
1566
+ const cacheKey = this.getContextCacheKey();
1567
+ const entry = this.contextCacheAdapter.get(cacheKey);
1568
+ if (!entry) {
1569
+ return null;
1570
+ }
1571
+ return entry.value;
1572
+ }
1573
+ /**
1574
+ * Remove the stored context for this agent
1575
+ * Clears any previously stored context from the cache
1576
+ */
1577
+ removeContext() {
1578
+ const cacheKey = this.getContextCacheKey();
1579
+ this.contextCacheAdapter.delete(cacheKey);
1580
+ this.logger.debug('Context removed from cache', { agentId: this.resolvedAgentId });
1581
+ }
1582
+ /**
1583
+ * Set context for this agent
1584
+ * Stores the context in cache and optionally sends it to the agent immediately
1585
+ * @param context - The context object to set
1586
+ * @param options - Optional settings
1587
+ * @param options.sendImmediately - If true, sends the context to the agent right away (default: false)
1588
+ * @returns Promise that resolves when context is set (and sent if sendImmediately is true)
1589
+ *
1590
+ * @example Set context without sending
1591
+ * ```typescript
1592
+ * agent.setContext({ userId: "123", plan: "premium" });
1593
+ * ```
1594
+ *
1595
+ * @example Set and send context immediately
1596
+ * ```typescript
1597
+ * await agent.setContext({ userId: "123", plan: "premium" }, { sendImmediately: true });
1598
+ * ```
1599
+ */
1600
+ async setContext(context, options) {
1601
+ this.storeContext(context);
1602
+ this.logger.info('Context set', { agentId: this.resolvedAgentId, context });
1603
+ if (options?.sendImmediately) {
1604
+ const contextMessage = createContextMessage({ context });
1605
+ await this.send(contextMessage);
1606
+ this.logger.debug('Context sent immediately after setContext', { agentId: this.resolvedAgentId });
1607
+ }
1608
+ }
1609
+ /**
1610
+ * Reset (clear) the context for this agent
1611
+ * Removes any stored context from the cache
1612
+ * This is an alias for removeContext() with additional logging
1613
+ *
1614
+ * @example
1615
+ * ```typescript
1616
+ * agent.resetContext();
1617
+ * ```
1618
+ */
1619
+ resetContext() {
1620
+ this.removeContext();
1621
+ this.logger.info('Context reset', { agentId: this.resolvedAgentId });
1622
+ }
1623
+ /**
1624
+ * Send stored context to the agent
1625
+ * This is called internally after reconnection to restore context
1626
+ * @returns Promise that resolves when context is sent (or immediately if no context)
1627
+ */
1628
+ async sendStoredContext() {
1629
+ const context = this.getContext();
1630
+ if (!context) {
1631
+ this.logger.debug('No stored context to send', { agentId: this.resolvedAgentId });
1632
+ return;
1633
+ }
1634
+ try {
1635
+ this.logger.debug('Sending stored context after reconnection', { agentId: this.resolvedAgentId, context });
1636
+ const contextMessage = createContextMessage({ context });
1637
+ await this.send(contextMessage);
1638
+ this.logger.info('Stored context sent successfully', { agentId: this.resolvedAgentId });
1639
+ }
1640
+ catch (error) {
1641
+ const err = error instanceof Error ? error : new Error(String(error));
1642
+ this.logger.error('Failed to send stored context', err, { agentId: this.resolvedAgentId });
1643
+ // Don't throw - allow the connection to continue even if context send fails
1644
+ }
1645
+ }
1646
+ /**
1647
+ * Setup connection event forwarding
1648
+ */
1649
+ setupConnectionEvents() {
1650
+ if (!this.connection) {
1651
+ return;
1652
+ }
1653
+ this.connection.on('connected', async (event) => {
1654
+ this.logger.info('Connection established', { sessionId: this.sessionId, agentId: this.resolvedAgentId });
1655
+ // Send token immediately if agent requires authentication
1656
+ await this.sendAuthTokenIfRequired();
1657
+ this.emit('connected', this.createAgentEventResponse('connected', {}, {
1658
+ timestamp: event.timestamp,
1659
+ }));
1660
+ // Flush queue when connected
1661
+ this.flushQueue();
1662
+ });
1663
+ this.connection.on('message', async (event) => {
1664
+ try {
1665
+ // Parse incoming message
1666
+ let sessionContext = {
1667
+ agentId: this.resolvedAgentId,
1668
+ sessionId: this.sessionId,
1669
+ customerName: "customer",
1670
+ agentName: this.agentDetails?.agentProfileDetails?.name,
1671
+ };
1672
+ const message = Message.fromJSON(event.data, sessionContext);
1673
+ this.logger.debug('Message received', {
1674
+ messageId: message.messageId,
1675
+ persona: message.persona,
1676
+ role: message.role
1677
+ });
1678
+ // Store message in transcript
1679
+ this.transcript.add(message, 'received', this.sessionId, this.resolvedAgentId);
1680
+ // Emit transcript update event
1681
+ this.emit('transcriptUpdate', {
1682
+ type: 'transcriptUpdate',
1683
+ timestamp: Date.now(),
1684
+ sessionId: this.sessionId,
1685
+ agentId: this.resolvedAgentId,
1686
+ payload: {
1687
+ entry: {
1688
+ message,
1689
+ direction: 'received',
1690
+ timestamp: message.timestamp || Date.now(),
1691
+ sessionId: this.sessionId,
1692
+ agentId: this.resolvedAgentId,
1693
+ },
1694
+ },
1695
+ });
1696
+ // Process message through handler chain
1697
+ const result = await this.messageProcessor.process(message);
1698
+ // Emit generic message event
1699
+ this.emit('message', this.createAgentEventResponse('message', {
1700
+ data: message,
1701
+ }));
1702
+ // Emit typed events based on handler result
1703
+ if (result) {
1704
+ if (result.type === 'agent_message') {
1705
+ this.logger.debug('Agent message processed', {
1706
+ messageId: message.messageId,
1707
+ from: result.from?.name
1708
+ });
1709
+ // Extract payload from result, excluding timestamp, sessionId, agentId
1710
+ const { timestamp, sessionId, agentId, ...payload } = result;
1711
+ this.emit('agentMessage', this.createAgentEventResponse('agentMessage', payload, {
1712
+ sessionId: this.sessionId ?? sessionId,
1713
+ agentId: this.resolvedAgentId ?? agentId,
1714
+ }));
1715
+ }
1716
+ else if (result.type === 'error_message') {
1717
+ // Create error object with message details
1718
+ const errorMessage = message.content || 'Error message received';
1719
+ const error = new Error(errorMessage);
1720
+ this.logger.error('Error message received', error, {
1721
+ messageId: message.messageId,
1722
+ errorCode: message.messageData?.error_code,
1723
+ content: message.content
1724
+ });
1725
+ // Emit errorMessage event
1726
+ this.emit('errorMessage', this.createAgentEventResponse('errorMessage', {
1727
+ message,
1728
+ error,
1729
+ }));
1730
+ // Optionally close connection on error (similar to reference implementation)
1731
+ // Consumers can handle connection closure in the errorMessage event handler if needed
1732
+ this.logger.debug('Error message event emitted, connection may need to be closed by consumer');
1733
+ }
1734
+ else if (result.type === 'heartbeat_processed') {
1735
+ this.logger.debug('Heartbeat processed', { messageId: message.messageId });
1736
+ // Extract payload from result, excluding timestamp, sessionId, agentId
1737
+ const { timestamp, sessionId, agentId, ...payload } = result;
1738
+ this.emit('heartbeat', this.createAgentEventResponse('heartbeat', payload, {
1739
+ sessionId: this.sessionId ?? sessionId,
1740
+ agentId: this.resolvedAgentId ?? agentId,
1741
+ }));
1742
+ }
1743
+ else if (result.type === 'token_refresh_required') {
1744
+ // Emit tokenExpiring event when transport layer requests token refresh
1745
+ this.logger.debug('Token refresh required by transport layer', { messageId: message.messageId });
1746
+ this.emit('tokenExpiring', this.createAgentEventResponse('tokenExpiring', {
1747
+ reason: 'transport_request',
1748
+ }));
1749
+ }
1750
+ }
1751
+ }
1752
+ catch (error) {
1753
+ // If message parsing fails, emit error but still emit raw message event
1754
+ const err = error instanceof Error ? error : new Error(String(error));
1755
+ this.logger.error('Failed to process message', err, { rawData: event.data });
1756
+ this.emit('error', this.createAgentEventResponse('error', {
1757
+ error: err,
1758
+ }));
1759
+ this.emit('message', this.createAgentEventResponse('message', {
1760
+ data: event.data,
1761
+ }));
1762
+ }
1763
+ });
1764
+ this.connection.on('error', (event) => {
1765
+ this.logger.error('Connection error', event.error, {
1766
+ sessionId: this.sessionId,
1767
+ agentId: this.resolvedAgentId
1768
+ });
1769
+ this.emit('error', this.createAgentEventResponse('error', {
1770
+ error: event.error,
1771
+ }, {
1772
+ timestamp: event.timestamp,
1773
+ }));
1774
+ });
1775
+ this.connection.on('closed', (event) => {
1776
+ this.logger.info('Connection closed', {
1777
+ code: event.code,
1778
+ reason: event.reason,
1779
+ sessionId: this.sessionId,
1780
+ agentId: this.resolvedAgentId
1781
+ });
1782
+ this.emit('closed', this.createAgentEventResponse('closed', {
1783
+ code: event.code,
1784
+ reason: event.reason,
1785
+ }, {
1786
+ timestamp: event.timestamp,
1787
+ }));
1788
+ });
1789
+ this.connection.on('stateChanged', (event) => {
1790
+ this.logger.debug('Connection state changed', {
1791
+ previousState: event.previousState,
1792
+ newState: event.state,
1793
+ sessionId: this.sessionId,
1794
+ agentId: this.resolvedAgentId
1795
+ });
1796
+ this.emit('stateChanged', this.createAgentEventResponse('stateChanged', {
1797
+ state: event.state,
1798
+ previousState: event.previousState,
1799
+ }));
1800
+ });
1801
+ }
1802
+ /**
1803
+ * Send authentication token to agent if authentication is required
1804
+ * This is called immediately after connection is established
1805
+ */
1806
+ async sendAuthTokenIfRequired() {
1807
+ // Check if agent requires authentication (isAuthenticated flag in agent details)
1808
+ const isAuthenticationRequired = this.agentDetails?.isAuthenticated;
1809
+ if (!isAuthenticationRequired) {
1810
+ this.logger.debug('Authentication not required, skipping token send', { agentId: this.resolvedAgentId });
1811
+ return;
1812
+ }
1813
+ try {
1814
+ // Get the current access token
1815
+ const authToken = await this.authService.getToken();
1816
+ if (!authToken) {
1817
+ this.logger.warn('Authentication required but no token available', { agentId: this.resolvedAgentId });
1818
+ return;
1819
+ }
1820
+ // Create and send the token message
1821
+ const tokenMessage = createTokenMessage({ token: authToken });
1822
+ const payload = JSON.stringify(tokenMessage);
1823
+ if (this.connection?.isConnected()) {
1824
+ await this.connection.send(payload);
1825
+ this.logger.debug('Authentication token sent to agent', { agentId: this.resolvedAgentId });
1826
+ }
1827
+ else {
1828
+ this.logger.warn('Cannot send auth token: connection not available', { agentId: this.resolvedAgentId });
1829
+ }
1830
+ }
1831
+ catch (error) {
1832
+ const err = error instanceof Error ? error : new Error(String(error));
1833
+ this.logger.error('Failed to send authentication token', err, { agentId: this.resolvedAgentId });
1834
+ // Don't throw - allow connection to continue even if token send fails
1835
+ }
1836
+ }
1837
+ /**
1838
+ * Queue a message for later sending
1839
+ */
1840
+ queueMessage(data, id) {
1841
+ try {
1842
+ const messageId = this.messageQueue.enqueue(data, id);
1843
+ this.logger.debug('Message queued', { messageId, queueSize: this.messageQueue.size() });
1844
+ return messageId;
1845
+ }
1846
+ catch (error) {
1847
+ const err = new MessageError('Failed to queue message', error instanceof Error ? error : new Error(String(error)));
1848
+ this.logger.error('Failed to queue message', err, { messageId: id });
1849
+ throw err;
1850
+ }
1851
+ }
1852
+ /**
1853
+ * Flush queued messages when connected
1854
+ */
1855
+ async flushQueue() {
1856
+ if (this.isFlushingQueue || !this.connection?.isConnected()) {
1857
+ return;
1858
+ }
1859
+ this.isFlushingQueue = true;
1860
+ let flushedCount = 0;
1861
+ const queueSize = this.messageQueue.size();
1862
+ if (queueSize > 0) {
1863
+ this.logger.debug('Flushing message queue', { queueSize });
1864
+ }
1865
+ try {
1866
+ while (!this.messageQueue.isEmpty() && this.connection?.isConnected()) {
1867
+ const message = this.messageQueue.peek();
1868
+ if (!message) {
1869
+ break;
1870
+ }
1871
+ try {
1872
+ await this.connection.send(message.data);
1873
+ this.messageQueue.dequeue();
1874
+ flushedCount++;
1875
+ // Small delay between messages to avoid overwhelming the connection
1876
+ await this.delay(5);
1877
+ }
1878
+ catch (error) {
1879
+ // If send fails, mark as attempted
1880
+ const shouldRetry = this.messageQueue.markAttempted(message.id);
1881
+ if (!shouldRetry) {
1882
+ // Max attempts reached, remove from queue
1883
+ this.messageQueue.remove(message.id);
1884
+ const err = new MessageError(`Failed to send queued message after max attempts: ${message.id}`, error instanceof Error ? error : new Error(String(error)));
1885
+ this.logger.error('Failed to send queued message after max attempts', err, { messageId: message.id });
1886
+ this.emit('error', this.createAgentEventResponse('error', {
1887
+ error: err,
1888
+ }));
1889
+ }
1890
+ else {
1891
+ // Wait a bit before retrying
1892
+ this.logger.debug('Retrying queued message', { messageId: message.id });
1893
+ await this.delay(100);
1894
+ }
1895
+ }
1896
+ }
1897
+ if (flushedCount > 0) {
1898
+ this.logger.info('Message queue flushed', { count: flushedCount });
1899
+ this.emit('queueFlushed', this.createAgentEventResponse('queueFlushed', {
1900
+ count: flushedCount,
1901
+ }));
1902
+ }
1903
+ }
1904
+ finally {
1905
+ this.isFlushingQueue = false;
1906
+ }
1907
+ }
1908
+ /**
1909
+ * Create an AgentEvent object with automatic timestamp, sessionId, and agentId
1910
+ * @param type - The event type
1911
+ * @param payload - The event-specific payload data
1912
+ * @param options - Optional overrides for timestamp, sessionId, or agentId
1913
+ * @returns A properly formatted AgentEvent object
1914
+ */
1915
+ createAgentEventResponse(type, payload, options) {
1916
+ return {
1917
+ type,
1918
+ timestamp: options?.timestamp ?? Date.now(),
1919
+ sessionId: options?.sessionId ?? this.sessionId,
1920
+ agentId: options?.agentId ?? this.resolvedAgentId,
1921
+ payload,
1922
+ };
1923
+ }
1924
+ /**
1925
+ * Generate a unique message ID
1926
+ */
1927
+ generateMessageId() {
1928
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1929
+ }
1930
+ /**
1931
+ * Delay utility
1932
+ */
1933
+ delay(ms) {
1934
+ return new Promise((resolve) => setTimeout(resolve, ms));
1935
+ }
1936
+ /**
1937
+ * Get the message processor instance
1938
+ * Allows adding custom handlers
1939
+ */
1940
+ getMessageProcessor() {
1941
+ return this.messageProcessor;
1942
+ }
1943
+ /**
1944
+ * Get the current access token from the authentication strategy
1945
+ * Returns the access token that the agent is currently using for authentication
1946
+ * @returns Promise resolving to the access token string, or null if no token is available
1947
+ *
1948
+ * @example
1949
+ * ```typescript
1950
+ * const token = await agent.getAccessToken();
1951
+ * if (token) {
1952
+ * // Use the token for external API calls
1953
+ * fetch('https://api.example.com/data', {
1954
+ * headers: { Authorization: `Bearer ${token}` }
1955
+ * });
1956
+ * }
1957
+ * ```
1958
+ */
1959
+ async getAccessToken() {
1960
+ this.logger.debug('Getting access token', { agentId: this.resolvedAgentId });
1961
+ try {
1962
+ const token = await this.authService.getToken();
1963
+ if (token) {
1964
+ this.logger.debug('Access token retrieved', { agentId: this.resolvedAgentId, tokenLength: token.length });
1965
+ }
1966
+ else {
1967
+ this.logger.debug('No access token available', { agentId: this.resolvedAgentId });
1968
+ }
1969
+ return token;
1970
+ }
1971
+ catch (error) {
1972
+ const err = error instanceof Error ? error : new Error(String(error));
1973
+ this.logger.error('Failed to get access token', err, { agentId: this.resolvedAgentId });
1974
+ throw error;
1975
+ }
1976
+ }
1977
+ /**
1978
+ * Update the access token at runtime
1979
+ * Use this method when you receive a tokenExpiring event to provide a new token
1980
+ * @param token - The new access token
1981
+ * @throws Error if the authentication strategy doesn't support token updates
1982
+ *
1983
+ * @example
1984
+ * ```typescript
1985
+ * agent.on('tokenExpiring', async (event) => {
1986
+ * const newToken = await fetchNewTokenFromServer();
1987
+ * await agent.updateAccessToken(newToken);
1988
+ * });
1989
+ * ```
1990
+ */
1991
+ async updateAccessToken(token) {
1992
+ this.logger.debug('Updating access token', { agentId: this.resolvedAgentId });
1993
+ await this.authService.updateToken(token);
1994
+ this.logger.info('Access token updated successfully', { agentId: this.resolvedAgentId });
1995
+ }
1996
+ /**
1997
+ * Set or update the session ID at runtime
1998
+ * Updates the sessionId property, which will be used for future connections and logging.
1999
+ *
2000
+ * **Note:** If the agent is currently connected, changing the sessionId will not automatically
2001
+ * switch the connection to the new session. You should either:
2002
+ * - Disconnect and reconnect with the new sessionId
2003
+ * - Use `restartConnection({ sessionId: newSessionId })` to restart with the new session
2004
+ *
2005
+ * @param sessionId - The new session ID to set
2006
+ *
2007
+ * @example
2008
+ * ```typescript
2009
+ * // Update sessionId after initialization
2010
+ * agent.setSessionId('new-session-id');
2011
+ *
2012
+ * // If connected, restart with the new sessionId
2013
+ * if (agent.isConnected()) {
2014
+ * await agent.restartConnection({ sessionId: 'new-session-id' });
2015
+ * }
2016
+ * ```
2017
+ */
2018
+ setSessionId(sessionId) {
2019
+ const oldSessionId = this.sessionId;
2020
+ this.sessionId = sessionId;
2021
+ this.logger.info('SessionId updated', {
2022
+ agentId: this.resolvedAgentId,
2023
+ oldSessionId,
2024
+ newSessionId: sessionId,
2025
+ isConnected: this.isConnected()
2026
+ });
2027
+ // Warn if connected - changing sessionId while connected won't affect the current connection
2028
+ if (this.isConnected()) {
2029
+ this.logger.warn('SessionId updated while connected. Current connection still uses old sessionId. Consider using restartConnection() to switch to the new session.', {
2030
+ agentId: this.resolvedAgentId,
2031
+ currentSessionId: oldSessionId,
2032
+ newSessionId: sessionId
2033
+ });
2034
+ }
2035
+ }
2036
+ }
2037
+ //# sourceMappingURL=AiAgent.js.map