@copilotz/chat-adapter 0.3.3 → 0.3.4

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
@@ -44,6 +44,15 @@ type RestMessage = {
44
44
  createdAt?: string;
45
45
  updatedAt?: string;
46
46
  };
47
+ type RestMessagePageInfo = {
48
+ hasMoreBefore: boolean;
49
+ oldestMessageId: string | null;
50
+ newestMessageId: string | null;
51
+ };
52
+ type RestMessagePage = {
53
+ data: RestMessage[];
54
+ pageInfo: RestMessagePageInfo;
55
+ };
47
56
  type StreamCallbacks = {
48
57
  onToken?: (token: string, isComplete: boolean, raw?: any, options?: {
49
58
  isReasoning?: boolean;
@@ -95,6 +104,10 @@ declare class CopilotzRequestError extends Error {
95
104
  declare function runCopilotzStream(options: RunOptions): Promise<CopilotzStreamResult>;
96
105
  declare function fetchThreads(userId: string, getRequestHeaders?: RequestHeadersProvider): Promise<RestThread[]>;
97
106
  declare function fetchThreadMessages(threadId: string, getRequestHeaders?: RequestHeadersProvider): Promise<RestMessage[]>;
107
+ declare function fetchThreadMessagesPage(threadId: string, options?: {
108
+ limit?: number;
109
+ before?: string | null;
110
+ }, getRequestHeaders?: RequestHeadersProvider): Promise<RestMessagePage>;
98
111
  declare function updateThread(threadId: string, updates: Partial<RestThread>, getRequestHeaders?: RequestHeadersProvider): Promise<any>;
99
112
  declare function deleteThread(threadId: string, getRequestHeaders?: RequestHeadersProvider): Promise<boolean>;
100
113
  declare const copilotzService: {
@@ -187,6 +200,8 @@ interface UseCopilotzOptions {
187
200
  declare function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onToolOutput, preferredAgentName, participants, targetAgentName, getRequestHeaders, eventInterceptor, runErrorInterceptor, }: UseCopilotzOptions): {
188
201
  messages: ChatMessage[];
189
202
  isMessagesLoading: boolean;
203
+ isLoadingOlderMessages: boolean;
204
+ messagePageInfo: RestMessagePageInfo;
190
205
  threads: ChatThread[];
191
206
  currentThreadId: string | null;
192
207
  isStreaming: boolean;
@@ -202,6 +217,7 @@ declare function useCopilotz({ userId, initialContext, bootstrap, defaultThreadN
202
217
  stopGeneration: () => void;
203
218
  fetchAndSetThreadsState: (uid: string, preferredExternalId?: string | null) => Promise<string | null | undefined>;
204
219
  loadThreadMessages: (threadId: string) => Promise<void>;
220
+ loadOlderMessages: () => Promise<void>;
205
221
  reset: () => void;
206
222
  };
207
223
 
@@ -281,4 +297,4 @@ type WithMetadata = {
281
297
  };
282
298
  declare function resolveAssetsInMessages<T extends WithMetadata>(messages: T[]): Promise<T[]>;
283
299
 
284
- export { CopilotzChat, CopilotzRequestError, type CopilotzStreamResult, type EventInterceptor, type EventInterceptorResult, type RenderSpecialState, type RequestHeadersProvider, type RunErrorInterceptor, type SpecialChatState, type SpecialStateControls, type UrlParamsConfig, type UrlState, type UseUrlStateReturn, copilotzService, deleteThread, fetchThreadMessages, fetchThreads, getAssetDataUrl, resolveAssetsInMessages, runCopilotzStream, updateThread, useCopilotz, useUrlState };
300
+ export { CopilotzChat, CopilotzRequestError, type CopilotzStreamResult, type EventInterceptor, type EventInterceptorResult, type RenderSpecialState, type RequestHeadersProvider, type RestMessagePage, type RestMessagePageInfo, type RunErrorInterceptor, type SpecialChatState, type SpecialStateControls, type UrlParamsConfig, type UrlState, type UseUrlStateReturn, copilotzService, deleteThread, fetchThreadMessages, fetchThreadMessagesPage, fetchThreads, getAssetDataUrl, resolveAssetsInMessages, runCopilotzStream, updateThread, useCopilotz, useUrlState };
package/dist/index.js CHANGED
@@ -130,6 +130,11 @@ var withAuthHeaders = async (headers = {}, getRequestHeaders) => {
130
130
  }
131
131
  return headers;
132
132
  };
133
+ var buildFallbackPageInfo = (data) => ({
134
+ hasMoreBefore: false,
135
+ oldestMessageId: data[0]?.id ?? null,
136
+ newestMessageId: data[data.length - 1]?.id ?? null
137
+ });
133
138
  var CopilotzRequestError = class extends Error {
134
139
  status;
135
140
  code;
@@ -549,8 +554,15 @@ async function fetchThreads(userId, getRequestHeaders) {
549
554
  return data;
550
555
  }
551
556
  async function fetchThreadMessages(threadId, getRequestHeaders) {
557
+ const page = await fetchThreadMessagesPage(threadId, void 0, getRequestHeaders);
558
+ return page.data;
559
+ }
560
+ async function fetchThreadMessagesPage(threadId, options, getRequestHeaders) {
552
561
  const params = new URLSearchParams();
553
- params.set("limit", "500");
562
+ params.set("limit", String(options?.limit ?? 50));
563
+ if (options?.before) {
564
+ params.set("before", options.before);
565
+ }
554
566
  const res = await fetch(
555
567
  apiUrl(`/v1/threads/${threadId}/messages?${params.toString()}`),
556
568
  {
@@ -566,14 +578,33 @@ async function fetchThreadMessages(threadId, getRequestHeaders) {
566
578
  errorText || `Failed to load thread messages (${res.status})`
567
579
  );
568
580
  }
569
- const data = await res.json();
570
- if (Array.isArray(data)) {
571
- return data;
581
+ const payload = await res.json();
582
+ if (Array.isArray(payload)) {
583
+ return {
584
+ data: payload,
585
+ pageInfo: buildFallbackPageInfo(payload)
586
+ };
572
587
  }
573
- if (Array.isArray(data?.data)) {
574
- return data.data;
588
+ if (Array.isArray(payload?.data)) {
589
+ const data = payload.data;
590
+ const rawPageInfo = payload?.pageInfo;
591
+ return {
592
+ data,
593
+ pageInfo: {
594
+ hasMoreBefore: rawPageInfo?.hasMoreBefore === true,
595
+ oldestMessageId: typeof rawPageInfo?.oldestMessageId === "string" ? rawPageInfo.oldestMessageId : data[0]?.id ?? null,
596
+ newestMessageId: typeof rawPageInfo?.newestMessageId === "string" ? rawPageInfo.newestMessageId : data[data.length - 1]?.id ?? null
597
+ }
598
+ };
575
599
  }
576
- return [];
600
+ return {
601
+ data: [],
602
+ pageInfo: {
603
+ hasMoreBefore: false,
604
+ oldestMessageId: null,
605
+ newestMessageId: null
606
+ }
607
+ };
577
608
  }
578
609
  async function updateThread(threadId, updates, getRequestHeaders) {
579
610
  const res = await fetch(apiUrl(`/v1/threads/${threadId}`), {
@@ -800,6 +831,12 @@ var messageAgentKey = (message) => {
800
831
  if (message.role !== "assistant") return null;
801
832
  return message.senderAgentId ?? message.senderName ?? null;
802
833
  };
834
+ var THREAD_MESSAGES_PAGE_SIZE = 50;
835
+ var createEmptyMessagePageInfo = () => ({
836
+ hasMoreBefore: false,
837
+ oldestMessageId: null,
838
+ newestMessageId: null
839
+ });
803
840
  var normalizeToolStatus = (status) => {
804
841
  if (status === "pending") return "pending";
805
842
  if (status === "running" || status === "processing") return "running";
@@ -917,6 +954,18 @@ var mergePersistedToolResults = (messages, updates) => {
917
954
  }
918
955
  return nextMessages;
919
956
  };
957
+ var prependUniqueMessages = (olderMessages, currentMessages) => {
958
+ if (olderMessages.length === 0) return currentMessages;
959
+ if (currentMessages.length === 0) return olderMessages;
960
+ const seen = /* @__PURE__ */ new Set();
961
+ const combined = [];
962
+ for (const message of [...olderMessages, ...currentMessages]) {
963
+ if (seen.has(message.id)) continue;
964
+ seen.add(message.id);
965
+ combined.push(message);
966
+ }
967
+ return combined;
968
+ };
920
969
  var convertServerMessage = (msg) => {
921
970
  const timestamp = msg.createdAt ? new Date(msg.createdAt).getTime() : nowTs();
922
971
  const metadata = msg.metadata ?? void 0;
@@ -1004,6 +1053,8 @@ function useCopilotz({
1004
1053
  const [currentThreadExternalId, setCurrentThreadExternalId] = useState2(null);
1005
1054
  const [messages, setMessages] = useState2([]);
1006
1055
  const [isMessagesLoading, setIsMessagesLoading] = useState2(false);
1056
+ const [isLoadingOlderMessages, setIsLoadingOlderMessages] = useState2(false);
1057
+ const [messagePageInfo, setMessagePageInfo] = useState2(createEmptyMessagePageInfo);
1007
1058
  const [isStreaming, setIsStreaming] = useState2(false);
1008
1059
  const [specialState, setSpecialState] = useState2(null);
1009
1060
  const [userContextSeed, setUserContextSeed] = useState2(initialContext || {});
@@ -1016,12 +1067,17 @@ function useCopilotz({
1016
1067
  const currentThreadIdRef = useRef2(currentThreadId);
1017
1068
  const currentThreadExternalIdRef = useRef2(currentThreadExternalId);
1018
1069
  const userContextSeedRef = useRef2(userContextSeed);
1070
+ const messagePageInfoRef = useRef2(messagePageInfo);
1071
+ const isLoadingOlderMessagesRef = useRef2(isLoadingOlderMessages);
1072
+ const persistedToolUpdatesRef = useRef2([]);
1019
1073
  threadsRef.current = threads;
1020
1074
  threadMetadataMapRef.current = threadMetadataMap;
1021
1075
  threadExternalIdMapRef.current = threadExternalIdMap;
1022
1076
  currentThreadIdRef.current = currentThreadId;
1023
1077
  currentThreadExternalIdRef.current = currentThreadExternalId;
1024
1078
  userContextSeedRef.current = userContextSeed;
1079
+ messagePageInfoRef.current = messagePageInfo;
1080
+ isLoadingOlderMessagesRef.current = isLoadingOlderMessages;
1025
1081
  preferredAgentRef.current = preferredAgentName ?? null;
1026
1082
  participantsRef.current = participants ?? null;
1027
1083
  targetAgentNameRef.current = targetAgentName ?? null;
@@ -1210,50 +1266,105 @@ function useCopilotz({
1210
1266
  return null;
1211
1267
  }
1212
1268
  }, [updateThreadsState, getRequestHeaders]);
1269
+ const prepareThreadMessages = useCallback2(async (rawMessages) => {
1270
+ const resolvedMessages = await resolveAssetsInMessages(rawMessages);
1271
+ resolvedMessages.forEach((msg) => {
1272
+ if (msg.senderType === "tool") {
1273
+ const metadata = msg.metadata;
1274
+ const output = metadata?.output ?? metadata;
1275
+ if (output) processToolOutput(output);
1276
+ }
1277
+ });
1278
+ const toolResultUpdates = resolvedMessages.map((msg) => extractToolResultUpdateFromMessage(msg)).filter((update) => update !== null);
1279
+ const viewMessages = resolvedMessages.filter((msg) => {
1280
+ const meta = msg.metadata ?? {};
1281
+ if (isInternalMessageMetadata(meta)) {
1282
+ return false;
1283
+ }
1284
+ const text = (typeof msg.content === "string" ? msg.content : "").trim();
1285
+ const hasText = text.length > 0;
1286
+ const hasToolCalls = extractToolCallsFromServerMessage(msg).length > 0;
1287
+ const hasAttachments = Array.isArray(meta.attachments) && meta.attachments.length > 0;
1288
+ if (msg.senderType === "tool") {
1289
+ return hasAttachments;
1290
+ }
1291
+ return hasText || hasToolCalls || hasAttachments;
1292
+ }).map(convertServerMessage);
1293
+ return {
1294
+ viewMessages,
1295
+ toolResultUpdates
1296
+ };
1297
+ }, [processToolOutput]);
1213
1298
  const loadThreadMessages = useCallback2(async (threadId) => {
1214
1299
  const requestId = messagesRequestRef.current + 1;
1215
1300
  messagesRequestRef.current = requestId;
1216
1301
  setIsMessagesLoading(true);
1302
+ setIsLoadingOlderMessages(false);
1303
+ setMessagePageInfo(createEmptyMessagePageInfo());
1304
+ persistedToolUpdatesRef.current = [];
1217
1305
  try {
1218
- const rawMessages = await fetchThreadMessages(threadId, getRequestHeaders);
1219
- const resolvedMessages = await resolveAssetsInMessages(rawMessages);
1306
+ const page = await fetchThreadMessagesPage(
1307
+ threadId,
1308
+ { limit: THREAD_MESSAGES_PAGE_SIZE },
1309
+ getRequestHeaders
1310
+ );
1311
+ const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
1220
1312
  if (messagesRequestRef.current !== requestId) return;
1221
- resolvedMessages.forEach((msg) => {
1222
- if (msg.senderType === "tool") {
1223
- const metadata = msg.metadata;
1224
- const output = metadata?.output ?? metadata;
1225
- if (output) processToolOutput(output);
1226
- }
1227
- });
1228
- const toolResultUpdates = resolvedMessages.map((msg) => extractToolResultUpdateFromMessage(msg)).filter((update) => update !== null);
1229
- const viewMessages = resolvedMessages.filter((msg) => {
1230
- const meta = msg.metadata ?? {};
1231
- if (isInternalMessageMetadata(meta)) {
1232
- return false;
1233
- }
1234
- const text = (typeof msg.content === "string" ? msg.content : "").trim();
1235
- const hasText = text.length > 0;
1236
- const hasToolCalls = extractToolCallsFromServerMessage(msg).length > 0;
1237
- const hasAttachments = Array.isArray(meta.attachments) && meta.attachments.length > 0;
1238
- if (msg.senderType === "tool") {
1239
- return hasAttachments;
1240
- }
1241
- return hasText || hasToolCalls || hasAttachments;
1242
- }).map(convertServerMessage);
1243
- const hydratedMessages = mergePersistedToolResults(viewMessages, toolResultUpdates);
1313
+ persistedToolUpdatesRef.current = toolResultUpdates;
1314
+ const hydratedMessages = mergePersistedToolResults(viewMessages, persistedToolUpdatesRef.current);
1244
1315
  setMessages(hydratedMessages);
1316
+ setMessagePageInfo(page.pageInfo);
1245
1317
  } catch (error) {
1246
1318
  if (isAbortError(error)) return;
1247
1319
  console.error(`Error loading messages for thread ${threadId}`, error);
1320
+ persistedToolUpdatesRef.current = [];
1321
+ setMessagePageInfo(createEmptyMessagePageInfo());
1248
1322
  } finally {
1249
1323
  if (messagesRequestRef.current === requestId) {
1250
1324
  setIsMessagesLoading(false);
1251
1325
  }
1252
1326
  }
1253
- }, [processToolOutput, getRequestHeaders]);
1327
+ }, [getRequestHeaders, prepareThreadMessages]);
1328
+ const loadOlderMessages = useCallback2(async () => {
1329
+ const threadId = currentThreadIdRef.current;
1330
+ const pageInfo = messagePageInfoRef.current;
1331
+ const before = pageInfo.oldestMessageId;
1332
+ if (!threadId || !before || !pageInfo.hasMoreBefore || isLoadingOlderMessagesRef.current) {
1333
+ return;
1334
+ }
1335
+ const requestId = messagesRequestRef.current;
1336
+ setIsLoadingOlderMessages(true);
1337
+ try {
1338
+ const page = await fetchThreadMessagesPage(
1339
+ threadId,
1340
+ { limit: THREAD_MESSAGES_PAGE_SIZE, before },
1341
+ getRequestHeaders
1342
+ );
1343
+ const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
1344
+ if (messagesRequestRef.current !== requestId) return;
1345
+ persistedToolUpdatesRef.current = [
1346
+ ...toolResultUpdates,
1347
+ ...persistedToolUpdatesRef.current
1348
+ ];
1349
+ setMessages((prev) => mergePersistedToolResults(
1350
+ prependUniqueMessages(viewMessages, prev),
1351
+ persistedToolUpdatesRef.current
1352
+ ));
1353
+ setMessagePageInfo(page.pageInfo);
1354
+ } catch (error) {
1355
+ if (isAbortError(error)) return;
1356
+ console.error(`Error loading older messages for thread ${threadId}`, error);
1357
+ } finally {
1358
+ if (messagesRequestRef.current === requestId) {
1359
+ setIsLoadingOlderMessages(false);
1360
+ }
1361
+ }
1362
+ }, [getRequestHeaders, prepareThreadMessages]);
1254
1363
  const handleSelectThread = useCallback2(async (threadId) => {
1255
1364
  setCurrentThreadId(threadId);
1256
1365
  setMessages([]);
1366
+ setMessagePageInfo(createEmptyMessagePageInfo());
1367
+ persistedToolUpdatesRef.current = [];
1257
1368
  const extMap = threadExternalIdMapRef.current;
1258
1369
  setCurrentThreadExternalId(extMap[threadId] ?? null);
1259
1370
  await loadThreadMessages(threadId);
@@ -1261,6 +1372,7 @@ function useCopilotz({
1261
1372
  const handleCreateThread = useCallback2((title) => {
1262
1373
  messagesRequestRef.current += 1;
1263
1374
  setIsMessagesLoading(false);
1375
+ setIsLoadingOlderMessages(false);
1264
1376
  const id = generateId();
1265
1377
  const now = nowTs();
1266
1378
  const newThread = {
@@ -1277,6 +1389,8 @@ function useCopilotz({
1277
1389
  setCurrentThreadId(id);
1278
1390
  setCurrentThreadExternalId(id);
1279
1391
  setMessages([]);
1392
+ setMessagePageInfo(createEmptyMessagePageInfo());
1393
+ persistedToolUpdatesRef.current = [];
1280
1394
  }, []);
1281
1395
  const handleRenameThread = useCallback2(async (threadId, newTitle) => {
1282
1396
  const trimmedTitle = newTitle.trim();
@@ -1346,6 +1460,8 @@ function useCopilotz({
1346
1460
  setCurrentThreadId(null);
1347
1461
  setCurrentThreadExternalId(null);
1348
1462
  setMessages([]);
1463
+ setMessagePageInfo(createEmptyMessagePageInfo());
1464
+ persistedToolUpdatesRef.current = [];
1349
1465
  }
1350
1466
  }
1351
1467
  if (!isPlaceholder) {
@@ -1898,6 +2014,8 @@ function useCopilotz({
1898
2014
  setThreadExternalIdMap((prev) => ({ ...prev, [bootstrapThreadExternalId]: bootstrapThreadExternalId }));
1899
2015
  setThreadMetadataMap((prev) => ({ ...prev, [bootstrapThreadExternalId]: {} }));
1900
2016
  setMessages([]);
2017
+ setMessagePageInfo(createEmptyMessagePageInfo());
2018
+ persistedToolUpdatesRef.current = [];
1901
2019
  setSpecialState(null);
1902
2020
  try {
1903
2021
  await sendCopilotzMessage({
@@ -1943,7 +2061,10 @@ function useCopilotz({
1943
2061
  setMessages([]);
1944
2062
  setUserContextSeed({});
1945
2063
  setIsMessagesLoading(false);
2064
+ setIsLoadingOlderMessages(false);
1946
2065
  setIsStreaming(false);
2066
+ setMessagePageInfo(createEmptyMessagePageInfo());
2067
+ persistedToolUpdatesRef.current = [];
1947
2068
  setSpecialState(null);
1948
2069
  abortControllerRef.current?.abort();
1949
2070
  }, []);
@@ -1984,6 +2105,8 @@ function useCopilotz({
1984
2105
  return {
1985
2106
  messages,
1986
2107
  isMessagesLoading,
2108
+ isLoadingOlderMessages,
2109
+ messagePageInfo,
1987
2110
  threads,
1988
2111
  currentThreadId,
1989
2112
  isStreaming,
@@ -1999,6 +2122,7 @@ function useCopilotz({
1999
2122
  stopGeneration: handleStop,
2000
2123
  fetchAndSetThreadsState,
2001
2124
  loadThreadMessages,
2125
+ loadOlderMessages,
2002
2126
  reset
2003
2127
  };
2004
2128
  }
@@ -2047,6 +2171,8 @@ var CopilotzChat = ({
2047
2171
  const {
2048
2172
  messages,
2049
2173
  isMessagesLoading,
2174
+ isLoadingOlderMessages,
2175
+ messagePageInfo,
2050
2176
  threads,
2051
2177
  currentThreadId,
2052
2178
  isStreaming,
@@ -2059,7 +2185,8 @@ var CopilotzChat = ({
2059
2185
  renameThread,
2060
2186
  archiveThread,
2061
2187
  deleteThread: deleteThread2,
2062
- stopGeneration
2188
+ stopGeneration,
2189
+ loadOlderMessages
2063
2190
  } = useCopilotz({
2064
2191
  userId,
2065
2192
  initialContext,
@@ -2160,6 +2287,9 @@ var CopilotzChat = ({
2160
2287
  {
2161
2288
  messages,
2162
2289
  isMessagesLoading,
2290
+ isLoadingOlderMessages,
2291
+ hasMoreMessagesBefore: messagePageInfo.hasMoreBefore,
2292
+ onLoadOlderMessages: loadOlderMessages,
2163
2293
  threads,
2164
2294
  currentThreadId,
2165
2295
  config: mergedConfig,
@@ -2188,6 +2318,7 @@ export {
2188
2318
  copilotzService,
2189
2319
  deleteThread,
2190
2320
  fetchThreadMessages,
2321
+ fetchThreadMessagesPage,
2191
2322
  fetchThreads,
2192
2323
  getAssetDataUrl,
2193
2324
  resolveAssetsInMessages,