@sendbird/ai-agent-messenger-react-native 1.8.0 → 1.9.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.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { Action } from '@sendbird/uikit-message-template';
2
2
  import { AIAgentChannelFilter } from '@sendbird/chat/aiAgent';
3
+ import { AIAgentGroupChannelListQuery } from '@sendbird/chat/aiAgent';
3
4
  import { AIAgentModule } from '@sendbird/chat/aiAgent';
5
+ import { BaseChannel } from '@sendbird/chat';
4
6
  import { BaseMessage } from '@sendbird/chat/message';
5
7
  import type * as CommunityDocumentPicker from '@react-native-documents/picker';
6
8
  import type * as CommunityImagePicker from 'react-native-image-picker';
7
9
  import { ComponentType } from 'react';
10
+ import { ConnectionHandler } from '@sendbird/chat';
8
11
  import { Context } from 'react';
9
12
  import { Conversation as Conversation_2 } from '@sendbird/chat/aiAgent';
10
13
  import { ConversationStatus } from '@sendbird/chat/aiAgent';
@@ -17,6 +20,9 @@ import { FlatList } from 'react-native';
17
20
  import { FlatListProps } from 'react-native';
18
21
  import { ForwardRefExoticComponent } from 'react';
19
22
  import { GroupChannel } from '@sendbird/chat/groupChannel';
23
+ import { GroupChannelChangelogs } from '@sendbird/chat/aiAgent';
24
+ import { GroupChannelHandler } from '@sendbird/chat/groupChannel';
25
+ import { GroupChannelListOrder } from '@sendbird/chat/groupChannel';
20
26
  import { GroupChannelModule } from '@sendbird/chat/groupChannel';
21
27
  import { JSX } from 'react/jsx-runtime';
22
28
  import { LayoutChangeEvent } from 'react-native';
@@ -262,7 +268,11 @@ declare interface AIAgentConfig {
262
268
  declare interface AIAgentContextValue {
263
269
  appId: string;
264
270
  aiAgentId: string;
271
+
272
+ /** @internal Do not use directly. This is an internal SDK instance. */
273
+ _aiAgentSDK: AIAgentInterface;
265
274
  chatSDK: SendbirdChatWith<[GroupChannelModule, AIAgentModule]>;
275
+ deskClient: DeskClientInterface;
266
276
 
267
277
  language: string;
268
278
  countryCode?: string;
@@ -374,6 +384,80 @@ declare interface AIAgentInfo {
374
384
  isUserFeedbackCommentOptionEnabled: boolean;
375
385
  }
376
386
 
387
+ /**
388
+ * Public interface for AIAgent SDK instance.
389
+ * This interface exposes the public API of the AIAgent class.
390
+ */
391
+ declare interface AIAgentInterface {
392
+ /** Sendbird application ID */
393
+ readonly appId: string;
394
+ /** AI Agent ID */
395
+ readonly aiAgentId: string;
396
+
397
+ /** The underlying Sendbird Chat SDK instance */
398
+ readonly chatSDK: ChatSDKType;
399
+ /** The current authenticated session. `null` if not authenticated */
400
+ readonly session: AIAgentSession | null;
401
+ /** The Desk client. Available after authenticate(). */
402
+ readonly deskClient: DeskClientInterface;
403
+ // TODO: Change to `AIAgentConfig` (non-optional) once the context migration lands and all usages are updated
404
+ /** The AI Agent configuration */
405
+ config: AIAgentConfig | undefined;
406
+ /** The query parameters for AI Agent */
407
+ queryParams?: AIAgentQueryParams;
408
+ /** The adapter for detecting online/offline network state */
409
+ readonly networkStateAdapter: NetworkStateAdapter | undefined;
410
+ /** The logger instance */
411
+ readonly logger: Loggable;
412
+ /** The event dispatcher */
413
+ readonly dispatcher: Dispatcher;
414
+ /** The cache instance */
415
+ readonly cache: AIAgentCache;
416
+ /** The language setting */
417
+ language: string;
418
+ /** The country code setting */
419
+ countryCode?: string;
420
+ /** The context object for AI Agent */
421
+ context?: Record<string, string>;
422
+
423
+ /** @internal Stats trackers for performance monitoring */
424
+ readonly statsTrackers: {
425
+ initialRender: ConversationInitialRenderStatsTracker;
426
+ };
427
+
428
+ /** Authenticates with the AI Agent server and establishes a session */
429
+ authenticate: (sessionInfo: ManualSessionInfo | AnonymousSessionInfo) => Promise<MessengerSettingsResponse>;
430
+ /** Disconnects from the AI Agent server and clears the session */
431
+ deauthenticate: () => Promise<void>;
432
+ /** Creates a new conversation with the AI Agent */
433
+ createConversation: (params: ConversationCreateParams) => Promise<string>;
434
+ /** Searches for open conversations that match the specified context */
435
+ searchConversation: (params: SearchConversationParams) => Promise<string[]>;
436
+ /** Closes an open conversation */
437
+ closeConversation: (channelUrl: string) => Promise<void>;
438
+ /** Creates a collection for paginating through conversations */
439
+ createConversationListCollection: (params?: ConversationListCollectionParams) => ConversationListCollection;
440
+ /** Patches (merges) context data for the specified channel */
441
+ patchContext: (channelUrl: string, context: Record<string, string>) => Promise<ContextObject>;
442
+ /** Replaces the entire context data for the specified channel */
443
+ updateContext: (channelUrl: string, context: Record<string, string>) => Promise<ContextObject>;
444
+ /** Retrieves the context data for the specified channel */
445
+ getContextObject: (channelUrl: string) => Promise<ContextObject>;
446
+
447
+ /** @internal Refreshes the active channel by re-requesting messenger settings */
448
+ refreshActiveChannel: (options?: { useKnownActiveChannelUrl?: boolean }) => Promise<string>;
449
+
450
+ /** Disposes all resources held by this instance (WebSocket, desk auth, event handlers). */
451
+ dispose: () => void;
452
+
453
+ /** Registers a handler for messenger settings updates */
454
+ onMessengerSettingsUpdated: (handler: (params: MessengerSettingsUpdatedParams) => void) => () => void;
455
+ /** Registers a handler for active channel updates */
456
+ onActiveChannelUpdated: (handler: (params: ActiveChannelUpdatedParams) => void) => () => void;
457
+ /** Registers a handler for auth token refresh events */
458
+ onAuthTokenRefreshed: (handler: (params: AuthTokenRefreshedParams) => void) => () => void;
459
+ }
460
+
377
461
  declare interface AIAgentMessengerSessionContextValue {
378
462
  userSessionInfo?: UserSessionInfo | ManualSessionInfo | AnonymousSessionInfo;
379
463
 
@@ -500,6 +584,68 @@ declare interface AIAgentQueryParams {
500
584
  conversationListParams?: ConversationListCollectionParams;
501
585
  }
502
586
 
587
+ /**
588
+ * @description Represents an authenticated session state. Created after successful authentication.
589
+ */
590
+ declare class AIAgentSession {
591
+ private readonly _sdkUser: User;
592
+ private readonly _userSession: UserSession;
593
+ private readonly _dispatcher: Dispatcher;
594
+ private _activeChannel: ActiveChannel;
595
+
596
+ constructor(params: AIAgentSessionParams) {
597
+ this._sdkUser = params.sdkUser;
598
+ this._userSession = params.userSession;
599
+ this._dispatcher = params.dispatcher;
600
+ this._activeChannel = params.activeChannel;
601
+ }
602
+
603
+ /**
604
+ * The authenticated Sendbird user.
605
+ */
606
+ get sdkUser(): User {
607
+ return this._sdkUser;
608
+ }
609
+
610
+ /**
611
+ * The current user session containing userId and authToken.
612
+ */
613
+ get userSession(): UserSession {
614
+ return this._userSession;
615
+ }
616
+
617
+ /**
618
+ * The currently active channel.
619
+ */
620
+ get activeChannel(): ActiveChannel {
621
+ return this._activeChannel;
622
+ }
623
+
624
+ /**
625
+ * Updates the active channel and dispatches ActiveChannelUpdated event.
626
+ * @internal
627
+ */
628
+ _updateActiveChannel = (channel: ActiveChannel): void => {
629
+ this._activeChannel = channel;
630
+ this._dispatcher.send(
631
+ DispatcherCommands.ActiveChannelUpdated({
632
+ channelUrl: channel.url,
633
+ status: channel.status,
634
+ conversationStatus: channel.conversationStatus,
635
+ }),
636
+ );
637
+ };
638
+
639
+ /**
640
+ * Updates the auth token when session is refreshed.
641
+ * @internal
642
+ */
643
+ _updateAuthToken = (authToken: string): void => {
644
+ this._userSession.authToken = authToken;
645
+ this._dispatcher.send(DispatcherCommands.AuthTokenRefreshed());
646
+ };
647
+ }
648
+
503
649
  /**
504
650
  * AI Agent session handler.
505
651
  * */
@@ -507,6 +653,13 @@ declare interface AIAgentSessionHandler extends SessionHandler {
507
653
  onExternalAuthTokenExpired?: (data: ExternalAuthTokenExpiredData) => void;
508
654
  }
509
655
 
656
+ declare interface AIAgentSessionParams {
657
+ sdkUser: User;
658
+ userSession: UserSession;
659
+ activeChannel: ActiveChannel;
660
+ dispatcher: Dispatcher;
661
+ }
662
+
510
663
  declare interface AIAgentStatPayload {
511
664
  key: string;
512
665
  value?: string;
@@ -623,6 +776,8 @@ declare interface AttachmentRules {
623
776
  getUploadSizeLimitInMB: (mimeType: string) => number;
624
777
  }
625
778
 
779
+ declare interface AuthTokenRefreshedParams {}
780
+
626
781
  declare abstract class BaseCommand {
627
782
  abstract type: CommandType;
628
783
  payload: any;
@@ -699,6 +854,8 @@ export declare interface BottomSheetTemplateProps {
699
854
  declare interface CameraOptions {
700
855
  }
701
856
 
857
+ declare type ChatSDKType = SendbirdChatWith<[GroupChannelModule, AIAgentModule]>;
858
+
702
859
  declare interface CitationInfo {
703
860
  embedding_id: number;
704
861
  source_type: string;
@@ -716,6 +873,7 @@ declare interface CommandPayloads {
716
873
  [CommandType.MessengerSettingsUpdated]: MessengerSettingsUpdatedParams;
717
874
  [CommandType.ActiveChannelUpdated]: ActiveChannelUpdatedParams;
718
875
  [CommandType.AnonymousSessionTokenExpired]: undefined;
876
+ [CommandType.AuthTokenRefreshed]: AuthTokenRefreshedParams;
719
877
  }
720
878
 
721
879
  declare enum CommandType {
@@ -723,6 +881,7 @@ declare enum CommandType {
723
881
  MessengerSettingsUpdated = 'messenger.settings.updated',
724
882
  ActiveChannelUpdated = 'active.channel.updated',
725
883
  AnonymousSessionTokenExpired = 'anon.token.expired',
884
+ AuthTokenRefreshed = 'auth.token.refreshed',
726
885
  }
727
886
 
728
887
  declare type CommunityDocumentPickerModule = typeof CommunityDocumentPicker;
@@ -1018,6 +1177,385 @@ export declare const ConversationLayout: {
1018
1177
 
1019
1178
  export declare const ConversationList: ({ children, ...props }: ConversationListContextProps) => JSX.Element;
1020
1179
 
1180
+ /**
1181
+ * A collection for managing AI agent group channels with real-time updates and pagination.
1182
+ * Automatically separates pinned and non-pinned channels, with pinned channels prioritized at the top.
1183
+ */
1184
+ declare class ConversationListCollection {
1185
+ /** The filter configuration used for this collection */
1186
+ readonly filter?: AIAgentGroupChannelFilter;
1187
+
1188
+ private _sdk: ChatSDKType;
1189
+ private _pinnedChannels: GroupChannel[];
1190
+ private _channels: GroupChannel[];
1191
+
1192
+ private _isDisposed = false;
1193
+ private _handlerId = `handler-id-${Date.now()}`;
1194
+ private _order: GroupChannelListOrder = GroupChannelListOrder.LATEST_LAST_MESSAGE;
1195
+
1196
+ private _limit: number;
1197
+ private _query: AIAgentGroupChannelListQuery;
1198
+
1199
+ private _token: string;
1200
+ private _timestamp: number;
1201
+ private _isSyncing: boolean;
1202
+
1203
+ private _channelHandler: GroupChannelHandler;
1204
+ private _connectionHandler: ConnectionHandler;
1205
+ private _collectionEventHandler?: ConversationListCollectionEventHandler;
1206
+ private _throttledOnChannelChanged: ThrottledFunction<(channel: BaseChannel) => void>;
1207
+
1208
+ /**
1209
+ * Creates a new ConversationListCollection instance.
1210
+ * @param sdk - The Sendbird Chat SDK instance
1211
+ * @param params - Configuration parameters for the collection
1212
+ */
1213
+ constructor(sdk: ChatSDKType, { filter, limit = 20 }: ConversationListCollectionParams) {
1214
+ this.filter = filter;
1215
+
1216
+ this._sdk = sdk;
1217
+ this._pinnedChannels = [];
1218
+ this._channels = [];
1219
+
1220
+ this._limit = limit;
1221
+ this._query = this._sdk.aiAgent.createMyGroupChannelListQuery({
1222
+ aiAgentChannelFilter: this.filter?.aiAgentChannelFilter,
1223
+ aiAgentConversationStatusFilter: this.filter?.aiAgentConversationStatusFilter,
1224
+ aiAgentIds: this.filter?.aiAgentIds,
1225
+ deskChannelFilter: this.filter?.deskChannelFilter,
1226
+ pinnedChannelUrls: this.filter?.pinnedChannelUrls,
1227
+ copilotConversationOnly: this.filter?.copilotConversationOnly,
1228
+ copilotSupportChannelUrl: this.filter?.copilotSupportChannelUrl,
1229
+ limit: this._limit,
1230
+ });
1231
+
1232
+ this._token = '';
1233
+ this._timestamp = Number.MAX_SAFE_INTEGER;
1234
+ this._isSyncing = false;
1235
+
1236
+ this._throttledOnChannelChanged = throttle(
1237
+ (channel: GroupChannel) => {
1238
+ if (this._query.belongsTo(channel)) {
1239
+ this._addChannelsToView([channel], false);
1240
+ } else {
1241
+ this._removeChannelsFromView([channel.url]);
1242
+ }
1243
+ },
1244
+ 250,
1245
+ { trailing: false, leading: true },
1246
+ );
1247
+
1248
+ this._channelHandler = new GroupChannelHandler({
1249
+ onChannelChanged: (channel) => {
1250
+ if (!channel.isGroupChannel()) return;
1251
+ this._throttledOnChannelChanged(channel);
1252
+ },
1253
+ onChannelDeleted: (channelUrl) => {
1254
+ this._removeChannelsFromView([channelUrl]);
1255
+ },
1256
+ });
1257
+
1258
+ this._connectionHandler = new ConnectionHandler({
1259
+ onReconnectSucceeded: () => {
1260
+ if (this._isDefaultChangelogSyncTimestampUpdated) this._syncChannelChangelogs();
1261
+ },
1262
+ });
1263
+
1264
+ this._sdk.addConnectionHandler(this._handlerId, this._connectionHandler);
1265
+ this._sdk.groupChannel.addGroupChannelHandler(this._handlerId, this._channelHandler);
1266
+ }
1267
+
1268
+ /**
1269
+ * Checks if the default changelog sync timestamp has been updated.
1270
+ */
1271
+ private get _isDefaultChangelogSyncTimestampUpdated() {
1272
+ return this._timestamp !== Number.MAX_SAFE_INTEGER;
1273
+ }
1274
+
1275
+ /**
1276
+ * Sets the default changelog sync timestamp based on the first non-pinned channel.
1277
+ * @param channels - Array of channels to analyze
1278
+ */
1279
+ private _setDefaultChangelogsSyncTimestamp(channels: GroupChannel[]) {
1280
+ const pinnedUrlsSet = new Set(this.filter?.pinnedChannelUrls ?? []);
1281
+ const firstChannel = channels.find((it) => {
1282
+ return !pinnedUrlsSet.has(it.url);
1283
+ });
1284
+
1285
+ let candidate: number;
1286
+ if (firstChannel) {
1287
+ candidate = firstChannel.lastMessage?.createdAt ?? firstChannel.createdAt;
1288
+ } else {
1289
+ candidate = this._query.lastResponseAt;
1290
+ }
1291
+
1292
+ if (this._timestamp > candidate) {
1293
+ this._timestamp = candidate;
1294
+ }
1295
+ }
1296
+
1297
+ /**
1298
+ * Synchronizes channel changelogs to keep the collection up-to-date.
1299
+ * @returns Promise that resolves to an empty array
1300
+ */
1301
+ private async _syncChannelChangelogs(): Promise<GroupChannel[]> {
1302
+ if (this._isDisposed) return [];
1303
+ if (this._isSyncing) return [];
1304
+
1305
+ try {
1306
+ this._isSyncing = true;
1307
+ let response: GroupChannelChangelogs;
1308
+
1309
+ if (this._token) {
1310
+ response = await this._sdk.aiAgent.getMyGroupChannelChangeLogsByToken(this._token, this.filter);
1311
+ } else {
1312
+ response = await this._sdk.aiAgent.getMyGroupChannelChangeLogsByTimestamp(this._timestamp, this.filter);
1313
+ }
1314
+ this._token = response.token;
1315
+ this._addChannelsToView(response.updatedChannels);
1316
+ this._removeChannelsFromView(response.deletedChannelUrls);
1317
+
1318
+ while (response.hasMore && !!response.token) {
1319
+ response = await this._sdk.aiAgent.getMyGroupChannelChangeLogsByToken(this._token, this.filter);
1320
+ this._token = response.token;
1321
+ this._addChannelsToView(response.updatedChannels);
1322
+ this._removeChannelsFromView(response.deletedChannelUrls);
1323
+ }
1324
+ } catch (error) {
1325
+ // Silently handle changelog sync errors to prevent disrupting the collection
1326
+ // The collection will continue to work with existing data
1327
+ } finally {
1328
+ this._isSyncing = false;
1329
+ }
1330
+ return [];
1331
+ }
1332
+
1333
+ /**
1334
+ * Upserts pinned channels maintaining the order specified in pinnedChannelUrls.
1335
+ * @param channels - Pinned channels to upsert
1336
+ * @returns Object containing added, updated channels and unmatched URLs
1337
+ */
1338
+ private _upsertPinnedChannelsToArray(channels: GroupChannel[]): {
1339
+ addedChannels: GroupChannel[];
1340
+ updatedChannels: GroupChannel[];
1341
+ unmatchedChannelUrls: string[];
1342
+ } {
1343
+ const pinnedUrls = this.filter?.pinnedChannelUrls ?? [];
1344
+ const pinnedIndexMap = new Map(pinnedUrls.map((url, index) => [url, index]));
1345
+
1346
+ const addedChannels: GroupChannel[] = [];
1347
+ const updatedChannels: GroupChannel[] = [];
1348
+ const unmatchedChannelUrls: string[] = [];
1349
+
1350
+ for (const channel of channels) {
1351
+ if (!this._query.belongsTo(channel)) {
1352
+ unmatchedChannelUrls.push(channel.url);
1353
+ continue;
1354
+ }
1355
+
1356
+ const targetIndex = pinnedIndexMap.get(channel.url);
1357
+ if (targetIndex === undefined) continue;
1358
+
1359
+ const existingPosition = indexOfChannel(this._pinnedChannels, channel);
1360
+ const isExisting = existingPosition >= 0;
1361
+
1362
+ if (isExisting) {
1363
+ this._pinnedChannels.splice(existingPosition, 1);
1364
+ updatedChannels.push(channel);
1365
+ } else {
1366
+ addedChannels.push(channel);
1367
+ }
1368
+
1369
+ const insertPosition = this._pinnedChannels.findIndex((existingChannel) => {
1370
+ const existingIndex = pinnedIndexMap.get(existingChannel.url) ?? Infinity;
1371
+ return existingIndex > targetIndex;
1372
+ });
1373
+
1374
+ const finalPosition = insertPosition === -1 ? this._pinnedChannels.length : insertPosition;
1375
+ this._pinnedChannels.splice(finalPosition, 0, channel);
1376
+ }
1377
+
1378
+ return { addedChannels, updatedChannels, unmatchedChannelUrls };
1379
+ }
1380
+
1381
+ /**
1382
+ * Upserts regular (non-pinned) channels with time-based sorting.
1383
+ * @param channels - Regular channels to upsert
1384
+ * @param forceAppend - Whether to force append channels at the end regardless of hasMore status
1385
+ * @returns Object containing added, updated channels and unmatched URLs
1386
+ */
1387
+ private _upsertRegularChannelsToArray(
1388
+ channels: GroupChannel[],
1389
+ forceAppend = false,
1390
+ ): { addedChannels: GroupChannel[]; updatedChannels: GroupChannel[]; unmatchedChannelUrls: string[] } {
1391
+ const unmatchedChannelUrls: string[] = [];
1392
+
1393
+ const addedChannels: GroupChannel[] = [];
1394
+ const updatedChannels: GroupChannel[] = [];
1395
+
1396
+ for (const channel of channels) {
1397
+ if (!this._query.belongsTo(channel)) {
1398
+ unmatchedChannelUrls.push(channel.url);
1399
+ continue;
1400
+ }
1401
+
1402
+ const oldPosition = indexOfChannel(this._channels, channel);
1403
+ const isNewChannel = oldPosition < 0;
1404
+ const shouldRemoveBeforeInsert = !isNewChannel;
1405
+
1406
+ if (shouldRemoveBeforeInsert) {
1407
+ this._channels.splice(oldPosition, 1);
1408
+ }
1409
+
1410
+ const insertionIndex = placeOfChannel(this._channels, channel, this._order).place;
1411
+ const isAtEnd = insertionIndex === this._channels.length;
1412
+
1413
+ if (isNewChannel) {
1414
+ if (isAtEnd) {
1415
+ if (forceAppend || !this.hasMore) {
1416
+ this._channels.push(channel);
1417
+ addedChannels.push(channel);
1418
+ }
1419
+ } else {
1420
+ this._channels.splice(insertionIndex, 0, channel);
1421
+ addedChannels.push(channel);
1422
+ }
1423
+ } else {
1424
+ this._channels.splice(insertionIndex, 0, channel);
1425
+ updatedChannels.push(channel);
1426
+ }
1427
+ }
1428
+
1429
+ return { addedChannels, updatedChannels, unmatchedChannelUrls };
1430
+ }
1431
+
1432
+ /**
1433
+ * Adds or updates channels in the collection, separating pinned and non-pinned channels.
1434
+ * @param channels - Channels to add or update
1435
+ * @param forceAppend - Whether to force append channels at the end
1436
+ */
1437
+ private _addChannelsToView(channels: GroupChannel[], forceAppend = false): void {
1438
+ const pinnedUrlsSet = new Set(this.filter?.pinnedChannelUrls ?? []);
1439
+
1440
+ const pinnedChannels: GroupChannel[] = [];
1441
+ const regularChannels: GroupChannel[] = [];
1442
+
1443
+ for (const channel of channels) {
1444
+ if (pinnedUrlsSet.has(channel.url)) {
1445
+ pinnedChannels.push(channel);
1446
+ } else {
1447
+ regularChannels.push(channel);
1448
+ }
1449
+ }
1450
+
1451
+ const pinnedResult = this._upsertPinnedChannelsToArray(pinnedChannels);
1452
+ const regularResult = this._upsertRegularChannelsToArray(regularChannels, forceAppend);
1453
+
1454
+ const addedChannels = pinnedResult.addedChannels.concat(regularResult.addedChannels);
1455
+ const updatedChannels = pinnedResult.updatedChannels.concat(regularResult.updatedChannels);
1456
+ const unmatchedChannelUrls = pinnedResult.unmatchedChannelUrls.concat(regularResult.unmatchedChannelUrls);
1457
+
1458
+ if (addedChannels.length > 0) this._collectionEventHandler?.onChannelsAdded?.({}, addedChannels);
1459
+ if (updatedChannels.length > 0) this._collectionEventHandler?.onChannelsUpdated?.({}, updatedChannels);
1460
+ if (unmatchedChannelUrls.length > 0) this._removeChannelsFromView(unmatchedChannelUrls);
1461
+ }
1462
+
1463
+ /**
1464
+ * Removes channels from the collection by their URLs.
1465
+ * @param channelUrls - Array of channel URLs to remove
1466
+ */
1467
+ private _removeChannelsFromView(channelUrls: string[]) {
1468
+ const pinnedUrlsSet = new Set(this.filter?.pinnedChannelUrls ?? []);
1469
+ const removedChannelUrls: string[] = [];
1470
+
1471
+ for (const channelUrl of channelUrls) {
1472
+ if (pinnedUrlsSet.has(channelUrl)) {
1473
+ const index = this._pinnedChannels.findIndex((channel) => channel.url === channelUrl);
1474
+ if (index >= 0) {
1475
+ removedChannelUrls.push(channelUrl);
1476
+ this._pinnedChannels.splice(index, 1);
1477
+ }
1478
+ } else {
1479
+ const index = this._channels.findIndex((channel) => channel.url === channelUrl);
1480
+ if (index >= 0) {
1481
+ removedChannelUrls.push(channelUrl);
1482
+ this._channels.splice(index, 1);
1483
+ }
1484
+ }
1485
+ }
1486
+
1487
+ if (removedChannelUrls.length > 0) this._collectionEventHandler?.onChannelsDeleted?.({}, removedChannelUrls);
1488
+ }
1489
+
1490
+ /**
1491
+ * Gets all channels in the collection.
1492
+ * Pinned channels are always returned first, followed by non-pinned channels.
1493
+ * @returns Array of GroupChannel objects
1494
+ */
1495
+ public get channels(): GroupChannel[] {
1496
+ return this._isDisposed ? [] : [...this._pinnedChannels, ...this._channels];
1497
+ }
1498
+
1499
+ /**
1500
+ * Indicates whether the collection has more channels to load.
1501
+ * @returns True if more channels can be loaded, false otherwise
1502
+ */
1503
+ public get hasMore(): boolean {
1504
+ return this._isDisposed ? false : this._query.hasNext;
1505
+ }
1506
+
1507
+ /**
1508
+ * Loads the next batch of channels from the server.
1509
+ * @returns Promise that resolves to an array of newly loaded channels
1510
+ */
1511
+ public async loadMore(): Promise<GroupChannel[]> {
1512
+ if (this._isDisposed) return [];
1513
+
1514
+ if (this.hasMore) {
1515
+ const channels = await this._query.next();
1516
+
1517
+ this._setDefaultChangelogsSyncTimestamp(channels);
1518
+ this._addChannelsToView(channels, true);
1519
+ return channels;
1520
+ }
1521
+
1522
+ return [];
1523
+ }
1524
+
1525
+ /**
1526
+ * Sets the event handler for collection updates.
1527
+ * @param handler - Event handler for channel collection changes
1528
+ */
1529
+ public setConversationListCollectionHandler(handler: ConversationListCollectionEventHandler): void {
1530
+ this._collectionEventHandler = handler;
1531
+ }
1532
+
1533
+ /**
1534
+ * Disposes the collection and cleans up all resources.
1535
+ * Stops all event handlers and clears all channels.
1536
+ */
1537
+ public dispose(): void {
1538
+ if (this._isDisposed) return;
1539
+
1540
+ this._isDisposed = true;
1541
+ this._throttledOnChannelChanged.cancel();
1542
+ this._collectionEventHandler = undefined;
1543
+ this._sdk.removeConnectionHandler(this._handlerId);
1544
+ this._sdk.groupChannel.removeGroupChannelHandler(this._handlerId);
1545
+ this._pinnedChannels.length = 0;
1546
+ this._channels.length = 0;
1547
+ }
1548
+ }
1549
+
1550
+ declare interface ConversationListCollectionEventHandler {
1551
+ /** Called when there are newly added {@link GroupChannel}s. */
1552
+ onChannelsAdded?: (context: ConversationListContext_2, channels: GroupChannel[]) => void;
1553
+ /** Called when there's an update in one or more of the {@link GroupChannel}s that `GroupChannelCollection` holds. */
1554
+ onChannelsUpdated?: (context: ConversationListContext_2, channels: GroupChannel[]) => void;
1555
+ /** Called when one or more of the {@link GroupChannel}s that `GroupChannelCollection` holds has been deleted. */
1556
+ onChannelsDeleted?: (context: ConversationListContext_2, channelUrls: string[]) => void;
1557
+ }
1558
+
1021
1559
  /**
1022
1560
  * Parameters for creating a ConversationListCollection instance.
1023
1561
  */
@@ -1030,6 +1568,8 @@ declare interface ConversationListCollectionParams {
1030
1568
 
1031
1569
  export declare const ConversationListContext: Context<ConversationListContextValue | null>;
1032
1570
 
1571
+ declare interface ConversationListContext_2 {}
1572
+
1033
1573
  declare interface ConversationListContextProps extends PropsWithChildren {
1034
1574
  conversationListLimit?: number;
1035
1575
  conversationListFilter?: Partial<AIAgentGroupChannelFilter>;
@@ -1216,6 +1756,68 @@ export declare interface DateSeparatorProps extends ViewProps {
1216
1756
  date?: Date | number;
1217
1757
  }
1218
1758
 
1759
+ export declare interface DeskClientInterface {
1760
+ /** Gets a ticket by its ID. */
1761
+ getTicket(id: number): Promise<DeskTicketInterface>;
1762
+ }
1763
+
1764
+ export declare interface DeskTicketAgent {
1765
+ /** The agent's user ID. */
1766
+ readonly userId: string;
1767
+ /** The agent's display name. */
1768
+ readonly name: string;
1769
+ /** The agent's profile image URL. */
1770
+ readonly profileUrl: string;
1771
+ }
1772
+
1773
+ export declare interface DeskTicketInterface {
1774
+ /** The ticket ID. */
1775
+ readonly id: number;
1776
+ /** The ticket title. */
1777
+ readonly title: string | null;
1778
+ /** The ticket status. Corresponds to the Desk Ticket's `status2` value. */
1779
+ readonly status: DeskTicketStatus;
1780
+ /** The agent assigned to this ticket. */
1781
+ readonly agent: DeskTicketAgent | null;
1782
+ /** The ticket priority. */
1783
+ readonly priority: DeskTicketPriority;
1784
+ /** The ticket group. */
1785
+ readonly group: number;
1786
+ /** The first response time in seconds. Returns -1 if there is no response. */
1787
+ readonly firstResponseTime: number;
1788
+ /** The timestamp when the ticket was issued. */
1789
+ readonly issuedAt: string | null;
1790
+ /** Custom fields set on this ticket. */
1791
+ readonly customFields: Record<string, string>;
1792
+ /** Refreshes all the data of this ticket from the server. */
1793
+ refresh(): Promise<DeskTicketInterface>;
1794
+ }
1795
+
1796
+ export declare enum DeskTicketPriority {
1797
+ URGENT = 'URGENT',
1798
+ HIGH = 'HIGH',
1799
+ /** This is the default. */
1800
+ MEDIUM = 'MEDIUM',
1801
+ LOW = 'LOW',
1802
+ }
1803
+
1804
+ export declare enum DeskTicketStatus {
1805
+ /** Ticket has been created, but the customer has not started chatting yet. */
1806
+ INITIALIZED = 'INITIALIZED',
1807
+ /** Ticket created via proactive chat. */
1808
+ PROACTIVE = 'PROACTIVE',
1809
+ /** No agent has been assigned yet. */
1810
+ PENDING = 'PENDING',
1811
+ /** An agent has been assigned and the ticket is being handled. */
1812
+ ACTIVE = 'ACTIVE',
1813
+ /** The ticket has been closed. */
1814
+ CLOSED = 'CLOSED',
1815
+ /** The ticket is in progress. */
1816
+ WORK_IN_PROGRESS = 'WORK_IN_PROGRESS',
1817
+ /** The assignment is in an idle state. */
1818
+ IDLE = 'IDLE',
1819
+ }
1820
+
1219
1821
  declare interface Dispatcher {
1220
1822
  send(command: BaseCommand): Promise<void>;
1221
1823
  subscribe<E extends CommandType>(eventType: E, callback: (payload: CommandPayloads[E]) => void | Promise<void>): void;
@@ -2767,6 +3369,8 @@ declare interface MessengerSettingsResponse {
2767
3369
  supported_file_mime_types: string[];
2768
3370
  max_attachment_count?: number;
2769
3371
  };
3372
+ language: string | null;
3373
+ country: string | null;
2770
3374
  }
2771
3375
 
2772
3376
  declare interface MessengerSettingsUpdatedParams {
@@ -3216,6 +3820,11 @@ declare type TextField = {
3216
3820
  };
3217
3821
  };
3218
3822
 
3823
+ declare interface ThrottledFunction<T extends (...args: any[]) => any> {
3824
+ (...args: Parameters<T>): void;
3825
+ cancel: () => void;
3826
+ }
3827
+
3219
3828
  declare interface TimerData {
3220
3829
  startTime: number | null;
3221
3830
  endTime: number | null;
@@ -3268,10 +3877,13 @@ export declare const useNativeAdapterContext: {
3268
3877
  displayName: string;
3269
3878
  };
3270
3879
 
3271
- declare type UserSession = {
3880
+ /**
3881
+ * User session containing authentication credentials.
3882
+ */
3883
+ declare interface UserSession {
3272
3884
  userId: string;
3273
3885
  authToken: string;
3274
- };
3886
+ }
3275
3887
 
3276
3888
  /**
3277
3889
  * @deprecated Please use `ManualSessionInfo` or `AnonymousSessionInfo` instead.