@constructor-io/constructorio-ui-asa 1.7.2 → 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.
Files changed (76) hide show
  1. package/README.md +21 -17
  2. package/dist/constructorio-ui-asa-bundled.js +17 -17
  3. package/lib/cjs/components/Button/Button.js +22 -3
  4. package/lib/cjs/components/Chat/AiMessage.js +2 -3
  5. package/lib/cjs/components/Chat/Chat.js +50 -9
  6. package/lib/cjs/components/CioAsa/CioAsa.js +6 -2
  7. package/lib/cjs/components/CioAsaProvider/CioAsaProvider.js +44 -3
  8. package/lib/cjs/components/ResultsBlock/ResultsGroup.js +5 -5
  9. package/lib/cjs/hooks/agentStream.js +113 -0
  10. package/lib/cjs/hooks/asaStreamHandlers.js +2 -1
  11. package/lib/cjs/hooks/persistence/backgroundTurns.js +121 -0
  12. package/lib/cjs/hooks/persistence/useChatPersistence.js +485 -0
  13. package/lib/cjs/hooks/persistence/useForeignStream.js +34 -0
  14. package/lib/cjs/hooks/persistence/useThreadList.js +45 -0
  15. package/lib/cjs/hooks/persistence/useWriteQueue.js +17 -0
  16. package/lib/cjs/hooks/useAsaResults.js +119 -159
  17. package/lib/cjs/hooks/useAsaTracking.js +33 -18
  18. package/lib/cjs/hooks/useIsMounted.js +15 -0
  19. package/lib/cjs/hooks/useLatest.js +10 -0
  20. package/lib/cjs/hooks/useViewportTracking.js +4 -3
  21. package/lib/cjs/index.js +3 -1
  22. package/lib/cjs/utils/chatRecordValidation.js +54 -0
  23. package/lib/cjs/utils/chatSession.js +98 -0
  24. package/lib/cjs/utils/chatThreads.js +248 -0
  25. package/lib/cjs/utils/clientOptions.js +11 -0
  26. package/lib/cjs/utils/localStoragePersistence.js +391 -0
  27. package/lib/cjs/utils/translate.js +1 -0
  28. package/lib/cjs/version.js +1 -1
  29. package/lib/mjs/components/Button/Button.js +26 -3
  30. package/lib/mjs/components/Chat/AiMessage.js +2 -3
  31. package/lib/mjs/components/Chat/Chat.js +48 -9
  32. package/lib/mjs/components/CioAsa/CioAsa.js +6 -2
  33. package/lib/mjs/components/CioAsaProvider/CioAsaProvider.js +44 -4
  34. package/lib/mjs/components/ResultsBlock/ResultsGroup.js +2 -1
  35. package/lib/mjs/hooks/agentStream.js +106 -0
  36. package/lib/mjs/hooks/asaStreamHandlers.js +1 -1
  37. package/lib/mjs/hooks/persistence/backgroundTurns.js +120 -0
  38. package/lib/mjs/hooks/persistence/useChatPersistence.js +474 -0
  39. package/lib/mjs/hooks/persistence/useForeignStream.js +31 -0
  40. package/lib/mjs/hooks/persistence/useThreadList.js +41 -0
  41. package/lib/mjs/hooks/persistence/useWriteQueue.js +14 -0
  42. package/lib/mjs/hooks/useAsaResults.js +113 -156
  43. package/lib/mjs/hooks/useAsaTracking.js +42 -17
  44. package/lib/mjs/hooks/useIsMounted.js +12 -0
  45. package/lib/mjs/hooks/useLatest.js +7 -0
  46. package/lib/mjs/hooks/useViewportTracking.js +3 -3
  47. package/lib/mjs/index.js +1 -0
  48. package/lib/mjs/utils/chatRecordValidation.js +51 -0
  49. package/lib/mjs/utils/chatSession.js +86 -0
  50. package/lib/mjs/utils/chatThreads.js +219 -0
  51. package/lib/mjs/utils/clientOptions.js +7 -0
  52. package/lib/mjs/utils/localStoragePersistence.js +373 -0
  53. package/lib/mjs/utils/translate.js +1 -0
  54. package/lib/mjs/version.js +1 -1
  55. package/lib/types/components/Button/Button.d.ts +3 -2
  56. package/lib/types/components/Chat/Chat.d.ts +20 -2
  57. package/lib/types/components/CioAsa/CioAsa.d.ts +1 -1
  58. package/lib/types/hooks/agentStream.d.ts +48 -0
  59. package/lib/types/hooks/asaStreamHandlers.d.ts +1 -0
  60. package/lib/types/hooks/persistence/backgroundTurns.d.ts +43 -0
  61. package/lib/types/hooks/persistence/useChatPersistence.d.ts +34 -0
  62. package/lib/types/hooks/persistence/useForeignStream.d.ts +9 -0
  63. package/lib/types/hooks/persistence/useThreadList.d.ts +9 -0
  64. package/lib/types/hooks/persistence/useWriteQueue.d.ts +5 -0
  65. package/lib/types/hooks/useAsaTracking.d.ts +6 -2
  66. package/lib/types/hooks/useIsMounted.d.ts +3 -0
  67. package/lib/types/hooks/useLatest.d.ts +3 -0
  68. package/lib/types/index.d.ts +3 -2
  69. package/lib/types/types.d.ts +170 -5
  70. package/lib/types/utils/chatRecordValidation.d.ts +2 -0
  71. package/lib/types/utils/chatSession.d.ts +45 -0
  72. package/lib/types/utils/chatThreads.d.ts +63 -0
  73. package/lib/types/utils/clientOptions.d.ts +5 -0
  74. package/lib/types/utils/localStoragePersistence.d.ts +26 -0
  75. package/lib/types/version.d.ts +1 -1
  76. package/package.json +2 -2
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareSnapshot = exports.adoptSession = exports.forkSession = exports.adoptStoredChat = exports.isOwnMessage = exports.nextMessageId = exports.resetConversation = exports.createChatSession = void 0;
4
+ const chatThreads_1 = require("./chatThreads");
5
+ const productNormalizer_1 = require("./productNormalizer");
6
+ function createChatSession(initialThreadId, normalizeItem = productNormalizer_1.normalizeItemToProduct) {
7
+ return {
8
+ normalizeItem,
9
+ serverThreadId: initialThreadId && !(0, chatThreads_1.isLocalThreadId)(initialThreadId) ? initialThreadId : null,
10
+ storageThreadId: null,
11
+ orphanIds: [],
12
+ createdAt: null,
13
+ lastSyncedAt: 0,
14
+ dirty: false,
15
+ interacted: false,
16
+ loadRequest: 0,
17
+ isStreaming: false,
18
+ foreignInFlight: false,
19
+ idCounter: 0,
20
+ idSuffix: (0, chatThreads_1.randomId)().slice(0, 6),
21
+ };
22
+ }
23
+ exports.createChatSession = createChatSession;
24
+ /** Forget the current conversation. Returns the id it was stored under, if any. */
25
+ function resetConversation(session) {
26
+ const storedId = session.storageThreadId;
27
+ session.serverThreadId = null;
28
+ session.storageThreadId = null;
29
+ session.orphanIds = [];
30
+ session.createdAt = null;
31
+ session.lastSyncedAt = 0;
32
+ session.dirty = false;
33
+ session.loadRequest += 1;
34
+ session.isStreaming = false;
35
+ session.foreignInFlight = false;
36
+ return storedId;
37
+ }
38
+ exports.resetConversation = resetConversation;
39
+ function nextMessageId(session) {
40
+ session.idCounter += 1;
41
+ return `msg-${session.idCounter}-${Date.now()}-${session.idSuffix}`;
42
+ }
43
+ exports.nextMessageId = nextMessageId;
44
+ /** Whether `messageId` was minted by this session rather than by another tab. */
45
+ function isOwnMessage(session, messageId) {
46
+ return messageId.endsWith(`-${session.idSuffix}`);
47
+ }
48
+ exports.isOwnMessage = isOwnMessage;
49
+ /** Makes a stored conversation the one this session continues. */
50
+ function adoptStoredChat(session, chat) {
51
+ session.storageThreadId = chat.threadId;
52
+ session.createdAt = chat.createdAt;
53
+ session.serverThreadId = (0, chatThreads_1.isLocalThreadId)(chat.threadId) ? null : chat.threadId;
54
+ session.idCounter = (0, chatThreads_1.nextMessageCounter)(chat.messages);
55
+ session.lastSyncedAt = chat.updatedAt;
56
+ }
57
+ exports.adoptStoredChat = adoptStoredChat;
58
+ /** A copy that keeps the conversation `session` holds while `session` itself moves on. */
59
+ function forkSession(session) {
60
+ return Object.assign(Object.assign({}, session), { orphanIds: [...session.orphanIds] });
61
+ }
62
+ exports.forkSession = forkSession;
63
+ /** Makes `session` continue the conversation `from` kept, showing `messages`. */
64
+ function adoptSession(session, from, messages) {
65
+ session.serverThreadId = from.serverThreadId;
66
+ session.storageThreadId = from.storageThreadId;
67
+ session.orphanIds = [...from.orphanIds];
68
+ session.createdAt = from.createdAt;
69
+ session.lastSyncedAt = from.lastSyncedAt;
70
+ session.idCounter = (0, chatThreads_1.nextMessageCounter)(messages);
71
+ }
72
+ exports.adoptSession = adoptSession;
73
+ /** Builds the record to write and moves the session onto its key; `staleIds` are the records to retire once stored. */
74
+ function prepareSnapshot(session, messages, now = Date.now()) {
75
+ var _a, _b, _c;
76
+ const previousId = session.storageThreadId;
77
+ const threadId = (_b = (_a = session.serverThreadId) !== null && _a !== void 0 ? _a : previousId) !== null && _b !== void 0 ? _b : (0, chatThreads_1.createLocalThreadId)();
78
+ const staleIds = [previousId !== threadId ? previousId : null, ...session.orphanIds].filter((id) => id !== null && id !== threadId);
79
+ // Strictly increasing so other tabs read a same-millisecond save as newer.
80
+ const updatedAt = Math.max(now, session.lastSyncedAt + 1);
81
+ const createdAt = (_c = session.createdAt) !== null && _c !== void 0 ? _c : updatedAt;
82
+ session.storageThreadId = threadId;
83
+ session.orphanIds = [];
84
+ session.createdAt = createdAt;
85
+ session.lastSyncedAt = updatedAt;
86
+ return {
87
+ staleIds: Array.from(new Set(staleIds)),
88
+ snapshot: {
89
+ version: chatThreads_1.PERSISTED_CHAT_VERSION,
90
+ threadId,
91
+ messages: (0, chatThreads_1.compactMessages)(messages, session.normalizeItem),
92
+ createdAt,
93
+ updatedAt,
94
+ owner: (0, chatThreads_1.getTabId)(),
95
+ },
96
+ };
97
+ }
98
+ exports.prepareSnapshot = prepareSnapshot;
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moveThreads = exports.foreignStreamRemainingMs = exports.isThreadStreaming = exports.findRekeyedThread = exports.saveThreadAndRetireStale = exports.mergeMessages = exports.compactMessages = exports.normalizeHydratedMessages = exports.isInFlight = exports.nextMessageCounter = exports.getThreadTitle = exports.isLocalThreadId = exports.getTabId = exports.createLocalThreadId = exports.randomId = exports.IN_FLIGHT_GRACE_MS = exports.DEFAULT_PERSISTENCE_TTL_MS = exports.DEFAULT_PERSISTENCE_KEY = exports.PERSISTED_CHAT_VERSION = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /** Stored record format; records with another version are ignored on read. */
6
+ exports.PERSISTED_CHAT_VERSION = 1;
7
+ /** Prefix of the `localStorage` key; the api key, domain and user id are appended to it. */
8
+ exports.DEFAULT_PERSISTENCE_KEY = 'cio-asa:chat';
9
+ /** One week, matching how long the agent itself remembers a thread. */
10
+ exports.DEFAULT_PERSISTENCE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
11
+ /** How long a stored answer written by another tab is still shown as "typing" before it is settled. */
12
+ exports.IN_FLIGHT_GRACE_MS = 60 * 1000;
13
+ /** Marks client-side thread ids, used before the server has assigned one. Never sent to the agent. */
14
+ const LOCAL_THREAD_PREFIX = 'local-';
15
+ /** Thread titles are the first question, cut to this many characters. */
16
+ const TITLE_MAX_LENGTH = 80;
17
+ let fallbackCounter = 0;
18
+ /** Random token for the ids that must not collide between tabs: threads, tabs and messages. */
19
+ function randomId() {
20
+ if (typeof crypto !== 'undefined') {
21
+ if (typeof crypto.randomUUID === 'function')
22
+ return crypto.randomUUID();
23
+ if (typeof crypto.getRandomValues === 'function') {
24
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
25
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
26
+ }
27
+ }
28
+ fallbackCounter += 1;
29
+ return `${Date.now().toString(36)}-${fallbackCounter.toString(36)}`;
30
+ }
31
+ exports.randomId = randomId;
32
+ /** Client-side thread id for a conversation the server has not named yet. */
33
+ function createLocalThreadId() {
34
+ return `${LOCAL_THREAD_PREFIX}${randomId()}`;
35
+ }
36
+ exports.createLocalThreadId = createLocalThreadId;
37
+ const TAB_ID_KEY = 'cio-asa:tab';
38
+ let tabId;
39
+ /** Whether this page load is a reload, as opposed to a new, duplicated or restored tab. */
40
+ function isReload() {
41
+ try {
42
+ const [entry] = performance.getEntriesByType('navigation');
43
+ return (entry === null || entry === void 0 ? void 0 : entry.type) === 'reload';
44
+ }
45
+ catch (_a) {
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * Per-tab id: survives a reload, differs between tabs, `undefined` on the server. A duplicated tab
51
+ * copies `sessionStorage`, so the id is read back on a reload only, and is kept there only while
52
+ * the page is hidden.
53
+ */
54
+ function getTabId() {
55
+ if (tabId)
56
+ return tabId;
57
+ if (typeof window === 'undefined')
58
+ return undefined;
59
+ const id = randomId();
60
+ tabId = id;
61
+ try {
62
+ const { sessionStorage } = window;
63
+ tabId = (isReload() && sessionStorage.getItem(TAB_ID_KEY)) || id;
64
+ sessionStorage.removeItem(TAB_ID_KEY);
65
+ const claimed = tabId;
66
+ window.addEventListener('pagehide', () => {
67
+ try {
68
+ sessionStorage.setItem(TAB_ID_KEY, claimed);
69
+ }
70
+ catch (_a) {
71
+ /* ignore */
72
+ }
73
+ });
74
+ window.addEventListener('pageshow', (event) => {
75
+ if (!event.persisted)
76
+ return;
77
+ try {
78
+ sessionStorage.removeItem(TAB_ID_KEY);
79
+ }
80
+ catch (_a) {
81
+ /* ignore */
82
+ }
83
+ });
84
+ }
85
+ catch (_a) {
86
+ /* sessionStorage blocked: the id lasts for this page only */
87
+ }
88
+ return tabId;
89
+ }
90
+ exports.getTabId = getTabId;
91
+ /** Whether `threadId` was made by `createLocalThreadId` rather than by the server. */
92
+ function isLocalThreadId(threadId) {
93
+ return typeof threadId === 'string' && threadId.startsWith(LOCAL_THREAD_PREFIX);
94
+ }
95
+ exports.isLocalThreadId = isLocalThreadId;
96
+ /** Title for a thread list: the first user message, truncated with an ellipsis. */
97
+ function getThreadTitle(messages) {
98
+ var _a, _b;
99
+ const first = (_b = (_a = messages.find((m) => m.role === 'user')) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '';
100
+ return first.length > TITLE_MAX_LENGTH ? `${first.slice(0, TITLE_MAX_LENGTH - 1)}…` : first;
101
+ }
102
+ exports.getThreadTitle = getThreadTitle;
103
+ /** Counter to continue message ids from after a restore, so new ids never collide with restored ones. */
104
+ function nextMessageCounter(messages) {
105
+ return messages.reduce((max, m) => {
106
+ const match = /^msg-(\d+)-/.exec(m.id);
107
+ return match ? Math.max(max, Number(match[1])) : max;
108
+ }, messages.length);
109
+ }
110
+ exports.nextMessageCounter = nextMessageCounter;
111
+ /** Whether the latest answer in `messages` has not finished streaming. */
112
+ function isInFlight(messages) {
113
+ const last = messages[messages.length - 1];
114
+ return (last === null || last === void 0 ? void 0 : last.status) === 'loading' || (last === null || last === void 0 ? void 0 : last.status) === 'streaming';
115
+ }
116
+ exports.isInFlight = isInFlight;
117
+ /** Settles answers stored mid-stream: `done` when they have content, an interrupted `error` otherwise. */
118
+ function normalizeHydratedMessages(messages) {
119
+ return messages.map((m) => {
120
+ var _a;
121
+ if (m.status !== 'loading' && m.status !== 'streaming')
122
+ return m;
123
+ const hasContent = Boolean(m.text) || Boolean((_a = m.groups) === null || _a === void 0 ? void 0 : _a.length) || Boolean(m.refinement);
124
+ return hasContent ? Object.assign(Object.assign({}, m), { status: 'done' }) : Object.assign(Object.assign({}, m), { status: 'error', interrupted: true });
125
+ });
126
+ }
127
+ exports.normalizeHydratedMessages = normalizeHydratedMessages;
128
+ /** Messages as stored: raw items replaced by the cards `normalize` makes; a restored group keeps its cards. */
129
+ function compactMessages(messages, normalize) {
130
+ return messages.map((m) => {
131
+ var _a;
132
+ return ((_a = m.groups) === null || _a === void 0 ? void 0 : _a.length)
133
+ ? Object.assign(Object.assign({}, m), { groups: m.groups.map((g) => {
134
+ var _a;
135
+ return (Object.assign(Object.assign({}, g), { searchResults: [], products: (_a = g.products) !== null && _a !== void 0 ? _a : g.searchResults.map(normalize) }));
136
+ }) }) : m;
137
+ });
138
+ }
139
+ exports.compactMessages = compactMessages;
140
+ const isUnsettled = (m) => m.status === 'loading' || m.status === 'streaming';
141
+ /**
142
+ * Both lists in stored order; a shared message takes the incoming version unless `preferStored`.
143
+ * A settled incoming version still replaces a stored one mid-stream: it is the later one by definition.
144
+ */
145
+ function mergeMessages(stored, incoming, preferStored = false) {
146
+ const incomingById = new Map(incoming.map((m) => [m.id, m]));
147
+ const storedIds = new Set(stored.map((m) => m.id));
148
+ return [
149
+ ...stored.map((m) => {
150
+ const update = incomingById.get(m.id);
151
+ if (!update)
152
+ return m;
153
+ if (!preferStored)
154
+ return update;
155
+ return isUnsettled(m) && !isUnsettled(update) ? update : m;
156
+ }),
157
+ ...incoming.filter((m) => !storedIds.has(m.id)),
158
+ ];
159
+ }
160
+ exports.mergeMessages = mergeMessages;
161
+ /**
162
+ * Saves `snapshot`, then deletes `staleIds` once the new record is read back. Stores may fail
163
+ * without throwing, so both steps are checked by reading: `saved` is whether the snapshot landed,
164
+ * `leftover` the stale ids still stored.
165
+ */
166
+ function saveThreadAndRetireStale(store, snapshot, staleIds) {
167
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
168
+ try {
169
+ yield store.saveThread(snapshot);
170
+ }
171
+ catch (_a) {
172
+ return { saved: false, leftover: staleIds };
173
+ }
174
+ const persisted = yield store.getThread(snapshot.threadId).catch(() => null);
175
+ if (!persisted || persisted.updatedAt < snapshot.updatedAt) {
176
+ return { saved: false, leftover: staleIds };
177
+ }
178
+ const retired = yield Promise.all(staleIds.map((id) => tslib_1.__awaiter(this, void 0, void 0, function* () {
179
+ yield store.deleteThread(id).catch(() => { });
180
+ return (yield store.getThread(id).catch(() => null)) === null;
181
+ })));
182
+ return { saved: true, leftover: staleIds.filter((_, i) => !retired[i]) };
183
+ });
184
+ }
185
+ exports.saveThreadAndRetireStale = saveThreadAndRetireStale;
186
+ /** Finds the server-keyed record another tab re-keyed a local thread into, by its first message. */
187
+ function findRekeyedThread(store, firstMessageId) {
188
+ var _a;
189
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
190
+ if (!firstMessageId)
191
+ return null;
192
+ const list = yield store.listThreads();
193
+ const candidates = list.filter((t) => !isLocalThreadId(t.threadId));
194
+ const loaded = yield Promise.all(candidates.map((t) => store.getThread(t.threadId)));
195
+ return (_a = loaded.find((c) => { var _a; return ((_a = c === null || c === void 0 ? void 0 : c.messages[0]) === null || _a === void 0 ? void 0 : _a.id) === firstMessageId; })) !== null && _a !== void 0 ? _a : null;
196
+ });
197
+ }
198
+ exports.findRekeyedThread = findRekeyedThread;
199
+ /** Whether a stored thread still reads as streaming: this tab's own always, another tab's only within the grace. */
200
+ function isThreadStreaming(chat, now = Date.now()) {
201
+ if (!isInFlight(chat.messages))
202
+ return false;
203
+ if (chat.owner !== undefined && chat.owner === getTabId())
204
+ return true;
205
+ return now - chat.updatedAt < exports.IN_FLIGHT_GRACE_MS;
206
+ }
207
+ exports.isThreadStreaming = isThreadStreaming;
208
+ /** Ms left to show a stored answer as streaming in another tab; `0` when it should be settled. */
209
+ function foreignStreamRemainingMs(chat, now = Date.now()) {
210
+ if (!isInFlight(chat.messages))
211
+ return 0;
212
+ if (chat.owner !== undefined && chat.owner === getTabId())
213
+ return 0;
214
+ return Math.max(0, exports.IN_FLIGHT_GRACE_MS - (now - chat.updatedAt));
215
+ }
216
+ exports.foreignStreamRemainingMs = foreignStreamRemainingMs;
217
+ /**
218
+ * Moves every thread of `from` into `to`; copies of the chat on screen are not copied, its owner
219
+ * saves it. A source record is deleted only once `to` holds it after every copy was made, so one
220
+ * copy evicting another, or a save that did not land, leaves the source in place.
221
+ */
222
+ function moveThreads(from, to, onScreen = {}) {
223
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
224
+ const { threadIds = [], firstMessageId } = onScreen;
225
+ const summaries = yield from.listThreads();
226
+ const chats = (yield Promise.all(summaries.map((t) => from.getThread(t.threadId)))).filter((chat) => chat !== null);
227
+ const isShown = (chat) => {
228
+ var _a;
229
+ return threadIds.includes(chat.threadId) ||
230
+ (firstMessageId !== undefined && ((_a = chat.messages[0]) === null || _a === void 0 ? void 0 : _a.id) === firstMessageId);
231
+ };
232
+ const toCopy = chats.filter((chat) => !isShown(chat));
233
+ yield Promise.all(toCopy.map((chat) => to.saveThread(chat)));
234
+ const targetList = yield to.listThreads();
235
+ const target = new Map((yield Promise.all(targetList.map((t) => to.getThread(t.threadId))))
236
+ .filter((chat) => chat !== null)
237
+ .map((chat) => [chat.threadId, chat]));
238
+ const shownSaved = Array.from(target.values()).some(isShown);
239
+ const moved = chats.filter((chat) => {
240
+ if (isShown(chat))
241
+ return shownSaved;
242
+ const copy = target.get(chat.threadId);
243
+ return copy !== undefined && copy.updatedAt >= chat.updatedAt;
244
+ });
245
+ yield Promise.all(moved.map((chat) => from.deleteThread(chat.threadId)));
246
+ });
247
+ }
248
+ exports.moveThreads = moveThreads;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readClientOptions = void 0;
4
+ /** `options` is private in the client's typings; read it here only, so a client bump fails a test, not storage keys. */
5
+ function readClientOptions(client) {
6
+ const { options } = (client !== null && client !== void 0 ? client : {});
7
+ if (!options || typeof options !== 'object')
8
+ return undefined;
9
+ return options;
10
+ }
11
+ exports.readClientOptions = readClientOptions;