@copilotz/chat-adapter 0.8.4 → 0.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.js CHANGED
@@ -1039,11 +1039,13 @@ var resolveHydratedMessageSender = (message, options = {}) => {
1039
1039
  });
1040
1040
  }
1041
1041
  if (type === "user") {
1042
+ const isCurrentUser = options.user?.id === externalId || options.user?.id === participantId || options.user?.id === storedId;
1043
+ const currentUserName = isCurrentUser ? clean(options.user?.name) : void 0;
1042
1044
  return defined({
1043
1045
  type: "user",
1044
1046
  id: externalId,
1045
1047
  externalId,
1046
- name: displayName,
1048
+ name: currentUserName ?? displayName,
1047
1049
  avatarUrl: clean(options.user?.avatarUrl),
1048
1050
  participantId: participantId ?? storedId
1049
1051
  });
@@ -1570,6 +1572,28 @@ var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${
1570
1572
  var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1571
1573
  var getEventPayload = (event) => event?.payload ?? event;
1572
1574
  var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
1575
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1576
+ var normalizeThreadTag = (value) => {
1577
+ if (!isRecord2(value)) return null;
1578
+ const id = typeof value.id === "string" ? value.id.trim() : "";
1579
+ const name = typeof value.name === "string" ? value.name.trim() : "";
1580
+ const color = typeof value.color === "string" && value.color.trim() ? value.color.trim() : void 0;
1581
+ if (!id || !name) return null;
1582
+ return color ? { id, name, color } : { id, name };
1583
+ };
1584
+ var getThreadTagsFromMetadata = (metadata) => {
1585
+ if (!isRecord2(metadata) || !isRecord2(metadata.public)) return [];
1586
+ const tags = metadata.public.tags;
1587
+ if (!Array.isArray(tags)) return [];
1588
+ return tags.map(normalizeThreadTag).filter((tag) => !!tag);
1589
+ };
1590
+ var patchMetadataPublicTags = (metadata, tags) => ({
1591
+ ...metadata ?? {},
1592
+ public: {
1593
+ ...isRecord2(metadata?.public) ? metadata.public : {},
1594
+ tags
1595
+ }
1596
+ });
1573
1597
  var THREAD_MESSAGES_PAGE_SIZE = 50;
1574
1598
  var createEmptyMessagePageInfo = () => ({
1575
1599
  hasMoreBefore: false,
@@ -1577,35 +1601,18 @@ var createEmptyMessagePageInfo = () => ({
1577
1601
  newestMessageId: null
1578
1602
  });
1579
1603
  var createPendingAssistantActivity = () => ({
1580
- items: [{
1581
- id: "thinking",
1582
- kind: "thinking",
1583
- status: "active",
1584
- startedAt: nowTs()
1585
- }]
1604
+ items: [
1605
+ {
1606
+ id: "thinking",
1607
+ kind: "thinking",
1608
+ status: "active",
1609
+ startedAt: nowTs()
1610
+ }
1611
+ ]
1586
1612
  });
1587
- function useCopilotz({
1588
- userId,
1589
- userName,
1590
- userAvatar,
1591
- assistantName,
1592
- agentOptions = [],
1593
- initialContext,
1594
- bootstrap,
1595
- defaultThreadName,
1596
- onToolOutput,
1597
- preferredAgentName,
1598
- participants,
1599
- targetAgentName,
1600
- getRequestHeaders,
1601
- eventInterceptor,
1602
- runErrorInterceptor
1603
- }) {
1604
- const {
1605
- state: urlState,
1606
- setThreadId: setUrlThreadId,
1607
- isEnabled: isUrlSyncEnabled
1608
- } = useUrlState();
1613
+ var getCurrentUserDisplayName = (explicitName, fallbackId) => explicitName?.trim() || fallbackId;
1614
+ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions = [], initialContext, bootstrap, defaultThreadName, onToolOutput, preferredAgentName, participants, targetAgentName, getRequestHeaders, eventInterceptor, runErrorInterceptor }) {
1615
+ const { state: urlState, setThreadId: setUrlThreadId, isEnabled: isUrlSyncEnabled } = useUrlState();
1609
1616
  const [threads, setThreads] = useState2([]);
1610
1617
  const [threadMetadataMap, setThreadMetadataMap] = useState2({});
1611
1618
  const [threadExternalIdMap, setThreadExternalIdMap] = useState2({});
@@ -1660,42 +1667,51 @@ function useCopilotz({
1660
1667
  setUserContextSeed((prev) => ({ ...prev, ...initialContext }));
1661
1668
  }
1662
1669
  }, [initialContext]);
1663
- const processToolOutput = useCallback2((output) => {
1664
- if (!output) return;
1665
- const contextPatch = {};
1666
- if (output.userContext && typeof output.userContext === "object") {
1667
- Object.assign(contextPatch, output.userContext);
1668
- }
1669
- if (Object.keys(contextPatch).length > 0) {
1670
- setUserContextSeed((prev) => ({ ...prev, ...contextPatch }));
1671
- }
1672
- onToolOutput?.(output);
1673
- }, [onToolOutput]);
1670
+ const processToolOutput = useCallback2(
1671
+ (output) => {
1672
+ if (!output) return;
1673
+ const contextPatch = {};
1674
+ if (output.userContext && typeof output.userContext === "object") {
1675
+ Object.assign(contextPatch, output.userContext);
1676
+ }
1677
+ if (Object.keys(contextPatch).length > 0) {
1678
+ setUserContextSeed((prev) => ({ ...prev, ...contextPatch }));
1679
+ }
1680
+ onToolOutput?.(output);
1681
+ },
1682
+ [onToolOutput]
1683
+ );
1674
1684
  const clearSpecialState = useCallback2(() => {
1675
1685
  setSpecialState(null);
1676
1686
  }, []);
1677
- const applyEventInterceptor = useCallback2((event) => {
1678
- if (!eventInterceptor) return void 0;
1679
- try {
1680
- const result = eventInterceptor(event);
1681
- if (result?.specialState) {
1682
- setSpecialState(result.specialState);
1687
+ const applyEventInterceptor = useCallback2(
1688
+ (event) => {
1689
+ if (!eventInterceptor) return void 0;
1690
+ try {
1691
+ const result = eventInterceptor(event);
1692
+ if (result?.specialState) {
1693
+ setSpecialState(result.specialState);
1694
+ }
1695
+ return result;
1696
+ } catch (error) {
1697
+ console.error("Error in Copilotz event interceptor", error);
1698
+ return void 0;
1683
1699
  }
1684
- return result;
1685
- } catch (error) {
1686
- console.error("Error in Copilotz event interceptor", error);
1687
- return void 0;
1688
- }
1689
- }, [eventInterceptor]);
1690
- const getSpecialStateFromError = useCallback2((error) => {
1691
- if (!runErrorInterceptor) return null;
1692
- try {
1693
- return runErrorInterceptor(error) ?? null;
1694
- } catch (interceptorError) {
1695
- console.error("Error in Copilotz run error interceptor", interceptorError);
1696
- return null;
1697
- }
1698
- }, [runErrorInterceptor]);
1700
+ },
1701
+ [eventInterceptor]
1702
+ );
1703
+ const getSpecialStateFromError = useCallback2(
1704
+ (error) => {
1705
+ if (!runErrorInterceptor) return null;
1706
+ try {
1707
+ return runErrorInterceptor(error) ?? null;
1708
+ } catch (interceptorError) {
1709
+ console.error("Error in Copilotz run error interceptor", interceptorError);
1710
+ return null;
1711
+ }
1712
+ },
1713
+ [runErrorInterceptor]
1714
+ );
1699
1715
  const handleStreamMessageEvent = useCallback2((event) => {
1700
1716
  const payload = getEventPayload(event);
1701
1717
  if (!payload) return;
@@ -1756,6 +1772,7 @@ function useCopilotz({
1756
1772
  updatedAt,
1757
1773
  messageCount: typeof thread.metadata?.messageCount === "number" ? thread.metadata.messageCount : 0,
1758
1774
  isArchived: thread.status === "archived",
1775
+ tags: getThreadTagsFromMetadata(thread.metadata),
1759
1776
  metadata: thread.metadata ?? void 0
1760
1777
  };
1761
1778
  });
@@ -1783,56 +1800,61 @@ function useCopilotz({
1783
1800
  setCurrentThreadExternalId(nextThreadId ? externalMap[nextThreadId] ?? null : null);
1784
1801
  return nextThreadId;
1785
1802
  }, []);
1786
- const fetchAndSetThreadsState = useCallback2(async (uid, preferredExternalId) => {
1787
- try {
1788
- const rawThreads = await fetchThreads(uid, getRequestHeaders);
1789
- return updateThreadsState(rawThreads, preferredExternalId);
1790
- } catch (error) {
1791
- if (isAbortError(error)) return;
1792
- console.error("Error loading threads", error);
1793
- return null;
1794
- }
1795
- }, [updateThreadsState, getRequestHeaders]);
1796
- const prepareThreadMessages = useCallback2(async (rawMessages) => {
1797
- return prepareHydratedMessages(rawMessages, {
1798
- senderOptions: senderOptionsRef.current,
1799
- createId: generateId,
1800
- now: nowTs,
1801
- onToolOutput: processToolOutput,
1802
- getRequestHeaders
1803
- });
1804
- }, [getRequestHeaders, processToolOutput]);
1805
- const loadThreadMessages = useCallback2(async (threadId) => {
1806
- const requestId = messagesRequestRef.current + 1;
1807
- messagesRequestRef.current = requestId;
1808
- setIsMessagesLoading(true);
1809
- setIsLoadingOlderMessages(false);
1810
- setMessagePageInfo(createEmptyMessagePageInfo());
1811
- persistedToolUpdatesRef.current = [];
1812
- liveToolUpdatesRef.current = [];
1813
- try {
1814
- const page = await fetchThreadMessagesPage(
1815
- threadId,
1816
- { limit: THREAD_MESSAGES_PAGE_SIZE },
1803
+ const fetchAndSetThreadsState = useCallback2(
1804
+ async (uid, preferredExternalId) => {
1805
+ try {
1806
+ const rawThreads = await fetchThreads(uid, getRequestHeaders);
1807
+ return updateThreadsState(rawThreads, preferredExternalId);
1808
+ } catch (error) {
1809
+ if (isAbortError(error)) return;
1810
+ console.error("Error loading threads", error);
1811
+ return null;
1812
+ }
1813
+ },
1814
+ [updateThreadsState, getRequestHeaders]
1815
+ );
1816
+ const prepareThreadMessages = useCallback2(
1817
+ async (rawMessages) => {
1818
+ return prepareHydratedMessages(rawMessages, {
1819
+ senderOptions: senderOptionsRef.current,
1820
+ createId: generateId,
1821
+ now: nowTs,
1822
+ onToolOutput: processToolOutput,
1817
1823
  getRequestHeaders
1818
- );
1819
- const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
1820
- if (messagesRequestRef.current !== requestId) return;
1821
- persistedToolUpdatesRef.current = toolResultUpdates;
1822
- const hydratedMessages = mergePersistedToolResults(viewMessages, persistedToolUpdatesRef.current);
1823
- setMessages(hydratedMessages);
1824
- setMessagePageInfo(page.pageInfo);
1825
- } catch (error) {
1826
- if (isAbortError(error)) return;
1827
- console.error(`Error loading messages for thread ${threadId}`, error);
1828
- persistedToolUpdatesRef.current = [];
1824
+ });
1825
+ },
1826
+ [getRequestHeaders, processToolOutput]
1827
+ );
1828
+ const loadThreadMessages = useCallback2(
1829
+ async (threadId) => {
1830
+ const requestId = messagesRequestRef.current + 1;
1831
+ messagesRequestRef.current = requestId;
1832
+ setIsMessagesLoading(true);
1833
+ setIsLoadingOlderMessages(false);
1829
1834
  setMessagePageInfo(createEmptyMessagePageInfo());
1830
- } finally {
1831
- if (messagesRequestRef.current === requestId) {
1832
- setIsMessagesLoading(false);
1835
+ persistedToolUpdatesRef.current = [];
1836
+ liveToolUpdatesRef.current = [];
1837
+ try {
1838
+ const page = await fetchThreadMessagesPage(threadId, { limit: THREAD_MESSAGES_PAGE_SIZE }, getRequestHeaders);
1839
+ const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
1840
+ if (messagesRequestRef.current !== requestId) return;
1841
+ persistedToolUpdatesRef.current = toolResultUpdates;
1842
+ const hydratedMessages = mergePersistedToolResults(viewMessages, persistedToolUpdatesRef.current);
1843
+ setMessages(hydratedMessages);
1844
+ setMessagePageInfo(page.pageInfo);
1845
+ } catch (error) {
1846
+ if (isAbortError(error)) return;
1847
+ console.error(`Error loading messages for thread ${threadId}`, error);
1848
+ persistedToolUpdatesRef.current = [];
1849
+ setMessagePageInfo(createEmptyMessagePageInfo());
1850
+ } finally {
1851
+ if (messagesRequestRef.current === requestId) {
1852
+ setIsMessagesLoading(false);
1853
+ }
1833
1854
  }
1834
- }
1835
- }, [getRequestHeaders, prepareThreadMessages]);
1855
+ },
1856
+ [getRequestHeaders, prepareThreadMessages]
1857
+ );
1836
1858
  const loadOlderMessages = useCallback2(async () => {
1837
1859
  const threadId = currentThreadIdRef.current;
1838
1860
  const pageInfo = messagePageInfoRef.current;
@@ -1843,21 +1865,11 @@ function useCopilotz({
1843
1865
  const requestId = messagesRequestRef.current;
1844
1866
  setIsLoadingOlderMessages(true);
1845
1867
  try {
1846
- const page = await fetchThreadMessagesPage(
1847
- threadId,
1848
- { limit: THREAD_MESSAGES_PAGE_SIZE, before },
1849
- getRequestHeaders
1850
- );
1868
+ const page = await fetchThreadMessagesPage(threadId, { limit: THREAD_MESSAGES_PAGE_SIZE, before }, getRequestHeaders);
1851
1869
  const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
1852
1870
  if (messagesRequestRef.current !== requestId) return;
1853
- persistedToolUpdatesRef.current = [
1854
- ...toolResultUpdates,
1855
- ...persistedToolUpdatesRef.current
1856
- ];
1857
- setMessages((prev) => mergePersistedToolResults(
1858
- prependUniqueMessages(viewMessages, prev),
1859
- persistedToolUpdatesRef.current
1860
- ));
1871
+ persistedToolUpdatesRef.current = [...toolResultUpdates, ...persistedToolUpdatesRef.current];
1872
+ setMessages((prev) => mergePersistedToolResults(prependUniqueMessages(viewMessages, prev), persistedToolUpdatesRef.current));
1861
1873
  setMessagePageInfo(page.pageInfo);
1862
1874
  } catch (error) {
1863
1875
  if (isAbortError(error)) return;
@@ -1868,15 +1880,18 @@ function useCopilotz({
1868
1880
  }
1869
1881
  }
1870
1882
  }, [getRequestHeaders, prepareThreadMessages]);
1871
- const handleSelectThread = useCallback2(async (threadId) => {
1872
- setCurrentThreadId(threadId);
1873
- setMessages([]);
1874
- setMessagePageInfo(createEmptyMessagePageInfo());
1875
- persistedToolUpdatesRef.current = [];
1876
- const extMap = threadExternalIdMapRef.current;
1877
- setCurrentThreadExternalId(extMap[threadId] ?? null);
1878
- await loadThreadMessages(threadId);
1879
- }, [loadThreadMessages]);
1883
+ const handleSelectThread = useCallback2(
1884
+ async (threadId) => {
1885
+ setCurrentThreadId(threadId);
1886
+ setMessages([]);
1887
+ setMessagePageInfo(createEmptyMessagePageInfo());
1888
+ persistedToolUpdatesRef.current = [];
1889
+ const extMap = threadExternalIdMapRef.current;
1890
+ setCurrentThreadExternalId(extMap[threadId] ?? null);
1891
+ await loadThreadMessages(threadId);
1892
+ },
1893
+ [loadThreadMessages]
1894
+ );
1880
1895
  const handleCreateThread = useCallback2((title) => {
1881
1896
  messagesRequestRef.current += 1;
1882
1897
  setIsMessagesLoading(false);
@@ -1892,7 +1907,10 @@ function useCopilotz({
1892
1907
  metadata: { pendingTitle: title?.trim() || void 0 }
1893
1908
  };
1894
1909
  setThreads((prev) => [newThread, ...prev]);
1895
- setThreadMetadataMap((prev) => ({ ...prev, [id]: { pendingTitle: title?.trim() || void 0 } }));
1910
+ setThreadMetadataMap((prev) => ({
1911
+ ...prev,
1912
+ [id]: { pendingTitle: title?.trim() || void 0 }
1913
+ }));
1896
1914
  setThreadExternalIdMap((prev) => ({ ...prev, [id]: id }));
1897
1915
  setCurrentThreadId(id);
1898
1916
  setCurrentThreadExternalId(id);
@@ -1900,89 +1918,117 @@ function useCopilotz({
1900
1918
  setMessagePageInfo(createEmptyMessagePageInfo());
1901
1919
  persistedToolUpdatesRef.current = [];
1902
1920
  }, []);
1903
- const handleRenameThread = useCallback2(async (threadId, newTitle) => {
1904
- const trimmedTitle = newTitle.trim();
1905
- if (!trimmedTitle) return;
1906
- setThreads(
1907
- (prev) => prev.map((t) => t.id === threadId ? { ...t, title: trimmedTitle, updatedAt: nowTs() } : t)
1908
- );
1909
- const extMap = threadExternalIdMapRef.current;
1910
- const isPlaceholder = extMap[threadId] === threadId;
1911
- if (isPlaceholder) {
1921
+ const handleRenameThread = useCallback2(
1922
+ async (threadId, newTitle) => {
1923
+ const trimmedTitle = newTitle.trim();
1924
+ if (!trimmedTitle) return;
1925
+ setThreads((prev) => prev.map((t) => t.id === threadId ? { ...t, title: trimmedTitle, updatedAt: nowTs() } : t));
1926
+ const extMap = threadExternalIdMapRef.current;
1927
+ const isPlaceholder = extMap[threadId] === threadId;
1928
+ if (isPlaceholder) {
1929
+ setThreadMetadataMap((prev) => ({
1930
+ ...prev,
1931
+ [threadId]: { ...prev[threadId], pendingTitle: trimmedTitle }
1932
+ }));
1933
+ } else {
1934
+ try {
1935
+ await updateThread(threadId, { name: trimmedTitle }, getRequestHeaders);
1936
+ } catch (error) {
1937
+ console.error("Failed to rename thread:", error);
1938
+ if (userId) {
1939
+ await fetchAndSetThreadsState(userId, currentThreadExternalIdRef.current);
1940
+ }
1941
+ }
1942
+ }
1943
+ },
1944
+ [userId, fetchAndSetThreadsState, getRequestHeaders]
1945
+ );
1946
+ const handleArchiveThread = useCallback2(
1947
+ async (threadId) => {
1948
+ const thread = threadsRef.current.find((t) => t.id === threadId);
1949
+ if (!thread) return;
1950
+ const newArchivedStatus = !thread.isArchived;
1951
+ setThreads((prev) => prev.map((t) => t.id === threadId ? { ...t, isArchived: newArchivedStatus, updatedAt: nowTs() } : t));
1952
+ const extMap = threadExternalIdMapRef.current;
1953
+ const isPlaceholder = extMap[threadId] === threadId;
1954
+ if (!isPlaceholder) {
1955
+ try {
1956
+ await updateThread(threadId, { status: newArchivedStatus ? "archived" : "active" }, getRequestHeaders);
1957
+ } catch (error) {
1958
+ console.error("Failed to archive thread:", error);
1959
+ if (userId) {
1960
+ await fetchAndSetThreadsState(userId, currentThreadExternalIdRef.current);
1961
+ }
1962
+ }
1963
+ }
1964
+ },
1965
+ [userId, fetchAndSetThreadsState, getRequestHeaders]
1966
+ );
1967
+ const handleUpdateThreadTags = useCallback2(
1968
+ async (threadId, tags) => {
1969
+ const extMap = threadExternalIdMapRef.current;
1970
+ const currentMetadata = threadMetadataMapRef.current[threadId];
1971
+ const nextMetadata = patchMetadataPublicTags(currentMetadata, tags);
1972
+ setThreads((prev) => prev.map((thread) => thread.id === threadId ? { ...thread, tags, metadata: nextMetadata, updatedAt: nowTs() } : thread));
1912
1973
  setThreadMetadataMap((prev) => ({
1913
1974
  ...prev,
1914
- [threadId]: { ...prev[threadId], pendingTitle: trimmedTitle }
1975
+ [threadId]: nextMetadata
1915
1976
  }));
1916
- } else {
1977
+ const isPlaceholder = extMap[threadId] === threadId;
1978
+ if (isPlaceholder) return;
1917
1979
  try {
1918
- await updateThread(threadId, { name: trimmedTitle }, getRequestHeaders);
1980
+ await updateThread(threadId, { metadata: nextMetadata }, getRequestHeaders);
1919
1981
  } catch (error) {
1920
- console.error("Failed to rename thread:", error);
1982
+ console.error("Failed to update thread tags:", error);
1921
1983
  if (userId) {
1922
1984
  await fetchAndSetThreadsState(userId, currentThreadExternalIdRef.current);
1923
1985
  }
1924
1986
  }
1925
- }
1926
- }, [userId, fetchAndSetThreadsState, getRequestHeaders]);
1927
- const handleArchiveThread = useCallback2(async (threadId) => {
1928
- const thread = threadsRef.current.find((t) => t.id === threadId);
1929
- if (!thread) return;
1930
- const newArchivedStatus = !thread.isArchived;
1931
- setThreads(
1932
- (prev) => prev.map((t) => t.id === threadId ? { ...t, isArchived: newArchivedStatus, updatedAt: nowTs() } : t)
1933
- );
1934
- const extMap = threadExternalIdMapRef.current;
1935
- const isPlaceholder = extMap[threadId] === threadId;
1936
- if (!isPlaceholder) {
1937
- try {
1938
- await updateThread(threadId, { status: newArchivedStatus ? "archived" : "active" }, getRequestHeaders);
1939
- } catch (error) {
1940
- console.error("Failed to archive thread:", error);
1941
- if (userId) {
1942
- await fetchAndSetThreadsState(userId, currentThreadExternalIdRef.current);
1987
+ },
1988
+ [userId, fetchAndSetThreadsState, getRequestHeaders]
1989
+ );
1990
+ const handleDeleteThread = useCallback2(
1991
+ async (threadId) => {
1992
+ const extMap = threadExternalIdMapRef.current;
1993
+ const isPlaceholder = extMap[threadId] === threadId;
1994
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
1995
+ setThreadMetadataMap((prev) => {
1996
+ const next = { ...prev };
1997
+ delete next[threadId];
1998
+ return next;
1999
+ });
2000
+ setThreadExternalIdMap((prev) => {
2001
+ const next = { ...prev };
2002
+ delete next[threadId];
2003
+ return next;
2004
+ });
2005
+ if (currentThreadIdRef.current === threadId) {
2006
+ const remaining = threadsRef.current.filter((t) => t.id !== threadId);
2007
+ if (remaining.length > 0) {
2008
+ setCurrentThreadId(remaining[0].id);
2009
+ setCurrentThreadExternalId(extMap[remaining[0].id] ?? null);
2010
+ await loadThreadMessages(remaining[0].id);
2011
+ } else {
2012
+ setCurrentThreadId(null);
2013
+ setCurrentThreadExternalId(null);
2014
+ setMessages([]);
2015
+ setMessagePageInfo(createEmptyMessagePageInfo());
2016
+ persistedToolUpdatesRef.current = [];
1943
2017
  }
1944
2018
  }
1945
- }
1946
- }, [userId, fetchAndSetThreadsState, getRequestHeaders]);
1947
- const handleDeleteThread = useCallback2(async (threadId) => {
1948
- const extMap = threadExternalIdMapRef.current;
1949
- const isPlaceholder = extMap[threadId] === threadId;
1950
- setThreads((prev) => prev.filter((t) => t.id !== threadId));
1951
- setThreadMetadataMap((prev) => {
1952
- const next = { ...prev };
1953
- delete next[threadId];
1954
- return next;
1955
- });
1956
- setThreadExternalIdMap((prev) => {
1957
- const next = { ...prev };
1958
- delete next[threadId];
1959
- return next;
1960
- });
1961
- if (currentThreadIdRef.current === threadId) {
1962
- const remaining = threadsRef.current.filter((t) => t.id !== threadId);
1963
- if (remaining.length > 0) {
1964
- setCurrentThreadId(remaining[0].id);
1965
- setCurrentThreadExternalId(extMap[remaining[0].id] ?? null);
1966
- await loadThreadMessages(remaining[0].id);
1967
- } else {
1968
- setCurrentThreadId(null);
1969
- setCurrentThreadExternalId(null);
1970
- setMessages([]);
1971
- setMessagePageInfo(createEmptyMessagePageInfo());
1972
- persistedToolUpdatesRef.current = [];
1973
- }
1974
- }
1975
- if (!isPlaceholder) {
1976
- try {
1977
- await deleteThread(threadId, getRequestHeaders);
1978
- } catch (error) {
1979
- console.error("Failed to delete thread:", error);
1980
- if (userId) {
1981
- await fetchAndSetThreadsState(userId, currentThreadExternalIdRef.current);
2019
+ if (!isPlaceholder) {
2020
+ try {
2021
+ await deleteThread(threadId, getRequestHeaders);
2022
+ } catch (error) {
2023
+ console.error("Failed to delete thread:", error);
2024
+ if (userId) {
2025
+ await fetchAndSetThreadsState(userId, currentThreadExternalIdRef.current);
2026
+ }
1982
2027
  }
1983
2028
  }
1984
- }
1985
- }, [userId, fetchAndSetThreadsState, loadThreadMessages, getRequestHeaders]);
2029
+ },
2030
+ [userId, fetchAndSetThreadsState, loadThreadMessages, getRequestHeaders]
2031
+ );
1986
2032
  const handleStop = useCallback2(() => {
1987
2033
  abortControllerRef.current?.abort();
1988
2034
  abortControllerRef.current = null;
@@ -2005,514 +2051,533 @@ function useCopilotz({
2005
2051
  ...typeof payload.fileName === "string" ? { fileName: payload.fileName } : {},
2006
2052
  ...typeof payload.size === "number" ? { size: payload.size } : {}
2007
2053
  };
2008
- setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? {
2009
- ...msg,
2010
- attachments: [...msg.attachments || [], mediaAttachment]
2011
- } : msg));
2054
+ setMessages(
2055
+ (prev) => prev.map(
2056
+ (msg) => msg.id === assistantMessageId ? {
2057
+ ...msg,
2058
+ attachments: [...msg.attachments || [], mediaAttachment]
2059
+ } : msg
2060
+ )
2061
+ );
2012
2062
  }, []);
2013
- const sendCopilotzMessage = useCallback2(async (params) => {
2014
- let currentAssistantId = params.assistantMessageId ?? generateId();
2015
- let currentAssistantSender = params.assistantSender;
2016
- params.onBeforeStart?.(currentAssistantId);
2017
- let hasStreamProgress = false;
2018
- const updateStreamingMessage = (partial, opts) => {
2019
- if (partial && partial.length > 0) {
2020
- hasStreamProgress = true;
2021
- }
2022
- const isReasoning = opts?.isReasoning ?? false;
2023
- const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
2024
- if (nextSender) {
2025
- currentAssistantSender = nextSender;
2026
- }
2027
- const nextAgentKey = currentAssistantSender?.agentId ?? currentAssistantSender?.id ?? null;
2028
- const applyUpdate = (msg) => {
2029
- return {
2030
- ...updateAssistantMessageToken(msg, {
2031
- partial,
2032
- isReasoning
2033
- }),
2034
- ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2063
+ const sendCopilotzMessage = useCallback2(
2064
+ async (params) => {
2065
+ let currentAssistantId = params.assistantMessageId ?? generateId();
2066
+ let currentAssistantSender = params.assistantSender;
2067
+ params.onBeforeStart?.(currentAssistantId);
2068
+ let hasStreamProgress = false;
2069
+ const updateStreamingMessage = (partial, opts) => {
2070
+ if (partial && partial.length > 0) {
2071
+ hasStreamProgress = true;
2072
+ }
2073
+ const isReasoning = opts?.isReasoning ?? false;
2074
+ const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
2075
+ if (nextSender) {
2076
+ currentAssistantSender = nextSender;
2077
+ }
2078
+ const nextAgentKey = currentAssistantSender?.agentId ?? currentAssistantSender?.id ?? null;
2079
+ const applyUpdate = (msg) => {
2080
+ return {
2081
+ ...updateAssistantMessageToken(msg, {
2082
+ partial,
2083
+ isReasoning
2084
+ }),
2085
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2086
+ };
2035
2087
  };
2088
+ setMessages((prev) => {
2089
+ const idx = prev.findIndex((m) => m.id === currentAssistantId);
2090
+ if (idx >= 0 && canAttachToStreamingAssistant(prev[idx], nextAgentKey)) {
2091
+ const msg = prev[idx];
2092
+ const next = applyUpdate(msg);
2093
+ if (msg.content === next.content && msg.activity === next.activity && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
2094
+ return prev;
2095
+ }
2096
+ const updated = [...prev];
2097
+ updated[idx] = next;
2098
+ return updated;
2099
+ }
2100
+ const last = prev[prev.length - 1];
2101
+ if (canAttachToStreamingAssistant(last, nextAgentKey)) {
2102
+ currentAssistantId = last.id;
2103
+ const next = applyUpdate(last);
2104
+ if (last.content === next.content && last.activity === next.activity && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
2105
+ return prev;
2106
+ }
2107
+ const updated = [...prev];
2108
+ updated[prev.length - 1] = next;
2109
+ return updated;
2110
+ }
2111
+ const lastStreamingBelongsToDifferentAgent = Boolean(nextAgentKey) && last?.role === "assistant" && last.isStreaming && Boolean(messageAgentKey(last)) && messageAgentKey(last) !== nextAgentKey;
2112
+ if (!prev.length || prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming || lastStreamingBelongsToDifferentAgent) {
2113
+ const newId = generateId();
2114
+ currentAssistantId = newId;
2115
+ const base = {
2116
+ id: newId,
2117
+ role: "assistant",
2118
+ content: "",
2119
+ timestamp: nowTs(),
2120
+ isStreaming: true,
2121
+ isComplete: false,
2122
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2123
+ };
2124
+ return [...prev, applyUpdate(base)];
2125
+ }
2126
+ return prev;
2127
+ });
2036
2128
  };
2037
- setMessages((prev) => {
2038
- const idx = prev.findIndex((m) => m.id === currentAssistantId);
2039
- if (idx >= 0 && canAttachToStreamingAssistant(prev[idx], nextAgentKey)) {
2129
+ const finalizeCurrentAssistantBubble = () => {
2130
+ setMessages((prev) => {
2131
+ const idx = prev.findIndex((m) => m.id === currentAssistantId);
2132
+ if (idx < 0) return prev;
2040
2133
  const msg = prev[idx];
2041
- const next = applyUpdate(msg);
2042
- if (msg.content === next.content && msg.activity === next.activity && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
2043
- return prev;
2044
- }
2134
+ if (!msg.isStreaming && msg.isComplete) return prev;
2045
2135
  const updated = [...prev];
2046
- updated[idx] = next;
2136
+ updated[idx] = closeAssistantMessage(msg);
2047
2137
  return updated;
2138
+ });
2139
+ };
2140
+ const curThreadId = currentThreadIdRef.current;
2141
+ const applyLiveToolResultUpdate = (update) => {
2142
+ let matched = false;
2143
+ setMessages((prev) => {
2144
+ const next = applyToolResultUpdateToMessages(prev, update, {
2145
+ isStreaming: true,
2146
+ isComplete: false
2147
+ });
2148
+ matched = next.matched;
2149
+ return next.matched ? next.messages : prev;
2150
+ });
2151
+ if (!matched) {
2152
+ liveToolUpdatesRef.current.push(update);
2048
2153
  }
2049
- const last = prev[prev.length - 1];
2050
- if (canAttachToStreamingAssistant(last, nextAgentKey)) {
2051
- currentAssistantId = last.id;
2052
- const next = applyUpdate(last);
2053
- if (last.content === next.content && last.activity === next.activity && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
2154
+ };
2155
+ const finalizeActiveAssistantTurn = (finalAnswer) => {
2156
+ setMessages((prev) => {
2157
+ const currentIdx = prev.findIndex((message2) => message2.id === currentAssistantId && message2.role === "assistant");
2158
+ const fallbackIdx = currentIdx >= 0 ? currentIdx : (() => {
2159
+ for (let i = prev.length - 1; i >= 0; i--) {
2160
+ if (prev[i].role === "assistant" && prev[i].isStreaming) {
2161
+ return i;
2162
+ }
2163
+ }
2164
+ return -1;
2165
+ })();
2166
+ if (fallbackIdx < 0) return prev;
2167
+ const message = prev[fallbackIdx];
2168
+ const nextMessage = finalizeAssistantMessage(message, finalAnswer);
2169
+ if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity) {
2054
2170
  return prev;
2055
2171
  }
2056
2172
  const updated = [...prev];
2057
- updated[prev.length - 1] = next;
2173
+ updated[fallbackIdx] = nextMessage;
2174
+ currentAssistantId = nextMessage.id;
2058
2175
  return updated;
2059
- }
2060
- const lastStreamingBelongsToDifferentAgent = Boolean(nextAgentKey) && last?.role === "assistant" && last.isStreaming && Boolean(messageAgentKey(last)) && messageAgentKey(last) !== nextAgentKey;
2061
- if (!prev.length || (prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming) || lastStreamingBelongsToDifferentAgent) {
2062
- const newId = generateId();
2063
- currentAssistantId = newId;
2064
- const base = {
2065
- id: newId,
2066
- role: "assistant",
2176
+ });
2177
+ };
2178
+ const toServerMessageFromEvent = async (event) => {
2179
+ if (!event) return null;
2180
+ const type = event?.type || "";
2181
+ const payload = event?.payload ?? event;
2182
+ if (type === "TOOL_CALL") {
2183
+ const parsedToolCall = extractLiveToolCall(payload);
2184
+ return {
2185
+ id: generateId(),
2186
+ threadId: curThreadId ?? "",
2187
+ senderType: "tool",
2067
2188
  content: "",
2068
- timestamp: nowTs(),
2069
- isStreaming: true,
2070
- isComplete: false,
2071
- ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2189
+ toolCalls: [
2190
+ {
2191
+ id: parsedToolCall.id ?? generateId(),
2192
+ name: parsedToolCall.name,
2193
+ args: parsedToolCall.arguments,
2194
+ ...parsedToolCall.result !== void 0 ? { output: parsedToolCall.result } : {},
2195
+ status: parsedToolCall.status
2196
+ }
2197
+ ]
2072
2198
  };
2073
- return [...prev, applyUpdate(base)];
2074
2199
  }
2075
- return prev;
2076
- });
2077
- };
2078
- const finalizeCurrentAssistantBubble = () => {
2079
- setMessages((prev) => {
2080
- const idx = prev.findIndex((m) => m.id === currentAssistantId);
2081
- if (idx < 0) return prev;
2082
- const msg = prev[idx];
2083
- if (!msg.isStreaming && msg.isComplete) return prev;
2084
- const updated = [...prev];
2085
- updated[idx] = closeAssistantMessage(msg);
2086
- return updated;
2087
- });
2088
- };
2089
- const curThreadId = currentThreadIdRef.current;
2090
- const applyLiveToolResultUpdate = (update) => {
2091
- let matched = false;
2092
- setMessages((prev) => {
2093
- const next = applyToolResultUpdateToMessages(prev, update, {
2094
- isStreaming: true,
2095
- isComplete: false
2200
+ return null;
2201
+ };
2202
+ const abortController = new AbortController();
2203
+ abortControllerRef.current?.abort();
2204
+ abortControllerRef.current = abortController;
2205
+ setIsStreaming(true);
2206
+ liveToolUpdatesRef.current = [];
2207
+ try {
2208
+ const normalizedUserMetadata = params.userMetadata ? JSON.parse(JSON.stringify(params.userMetadata)) : void 0;
2209
+ const contextSeed = userContextSeedRef.current;
2210
+ const contextMetadata = contextSeed ? JSON.parse(JSON.stringify(contextSeed)) : void 0;
2211
+ const requestContent = params.content && params.content.length > 0 ? params.content : "";
2212
+ const metadataKey = params.threadId ?? params.threadExternalId ?? void 0;
2213
+ const currentThreadMetadataMap = threadMetadataMapRef.current;
2214
+ const messageMetadata = metadataKey ? currentThreadMetadataMap[metadataKey]?.userContext : void 0;
2215
+ const threadMetadata = metadataKey ? currentThreadMetadataMap[metadataKey] : void 0;
2216
+ const mergedMetadata = {
2217
+ ...messageMetadata ?? {},
2218
+ ...params.metadata ?? {}
2219
+ };
2220
+ const finalMetadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
2221
+ await runCopilotzStream({
2222
+ threadId: params.threadId ?? void 0,
2223
+ threadExternalId: params.threadExternalId ?? void 0,
2224
+ content: requestContent,
2225
+ user: {
2226
+ externalId: params.userId,
2227
+ name: params.userName ?? params.userId,
2228
+ metadata: {
2229
+ ...contextMetadata ? contextMetadata : {},
2230
+ ...normalizedUserMetadata ?? {}
2231
+ }
2232
+ },
2233
+ attachments: params.attachments,
2234
+ metadata: finalMetadata,
2235
+ threadMetadata: params.threadMetadata ?? threadMetadata,
2236
+ toolCalls: params.toolCalls,
2237
+ selectedAgent: params.agentName ?? preferredAgentRef.current ?? null,
2238
+ participants: participantsRef.current,
2239
+ targetAgent: targetAgentNameRef.current,
2240
+ getRequestHeaders,
2241
+ onToken: (token, _isComplete, raw, opts) => updateStreamingMessage(token, {
2242
+ ...opts,
2243
+ agent: raw?.payload?.agent ?? raw?.agent ?? null
2244
+ }),
2245
+ onMessageEvent: async (event) => {
2246
+ const intercepted = applyEventInterceptor(event);
2247
+ if (intercepted?.handled) {
2248
+ return;
2249
+ }
2250
+ const type = event?.type || "";
2251
+ const payload = getEventPayload(event);
2252
+ if (type === "TOOL_RESULT") {
2253
+ processToolOutput(payload ?? {});
2254
+ applyLiveToolResultUpdate(extractLiveToolResultUpdate(payload ?? {}));
2255
+ return;
2256
+ }
2257
+ if (type === "LLM_RESULT") {
2258
+ const finalAnswer = typeof payload?.answer === "string" ? payload.answer : void 0;
2259
+ finalizeActiveAssistantTurn(finalAnswer);
2260
+ return;
2261
+ }
2262
+ if (type === "MESSAGE" || type === "NEW_MESSAGE") {
2263
+ return;
2264
+ }
2265
+ if (type === "TOOL_CALL") {
2266
+ const parsedToolCall = extractLiveToolCall(payload ?? {});
2267
+ const eventSender = resolveLiveEventSender(event, senderOptionsRef.current);
2268
+ currentAssistantSender = eventSender;
2269
+ const eventAgentKey = currentAssistantSender.agentId ?? currentAssistantSender.id;
2270
+ const callId = parsedToolCall.id ?? generateId();
2271
+ const toolName = parsedToolCall.name;
2272
+ const bufferedUpdates = liveToolUpdatesRef.current;
2273
+ const matchingUpdateIndex = bufferedUpdates.findIndex((upd) => matchesToolResultUpdate({ id: callId, name: toolName }, upd));
2274
+ const bufferedUpdate = matchingUpdateIndex >= 0 ? bufferedUpdates[matchingUpdateIndex] : void 0;
2275
+ if (matchingUpdateIndex >= 0) {
2276
+ bufferedUpdates.splice(matchingUpdateIndex, 1);
2277
+ }
2278
+ const initialStatus = bufferedUpdate ? bufferedUpdate.status : parsedToolCall.status;
2279
+ const initialResult = bufferedUpdate && bufferedUpdate.result !== void 0 ? bufferedUpdate.result : parsedToolCall.result;
2280
+ const endTime = bufferedUpdate?.endTime;
2281
+ setMessages(
2282
+ (prev) => (() => {
2283
+ const canHostActivity = (message) => {
2284
+ if (!message) return false;
2285
+ return message.role === "assistant" && message.isStreaming && message.content.trim().length === 0 && !message.attachments?.length;
2286
+ };
2287
+ const appendToolCall = (msg) => ({
2288
+ ...appendAssistantToolCall(msg, {
2289
+ id: callId,
2290
+ name: toolName,
2291
+ arguments: parsedToolCall.arguments,
2292
+ ...initialResult !== void 0 ? { result: initialResult } : {},
2293
+ status: initialStatus,
2294
+ startTime: Date.now(),
2295
+ ...endTime !== void 0 ? { endTime } : {}
2296
+ })
2297
+ });
2298
+ const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming && canHostActivity(message));
2299
+ if (currentIdx >= 0) {
2300
+ const next = [...prev];
2301
+ next[currentIdx] = appendToolCall({
2302
+ ...next[currentIdx],
2303
+ isStreaming: true,
2304
+ isComplete: false,
2305
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2306
+ });
2307
+ return next;
2308
+ }
2309
+ const last = prev[prev.length - 1];
2310
+ if (canHostActivity(last) && canAttachToStreamingAssistant(last, eventAgentKey)) {
2311
+ currentAssistantId = last.id;
2312
+ const next = [...prev];
2313
+ next[prev.length - 1] = appendToolCall({
2314
+ ...last,
2315
+ isStreaming: true,
2316
+ isComplete: false,
2317
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2318
+ });
2319
+ return next;
2320
+ }
2321
+ const newId = generateId();
2322
+ currentAssistantId = newId;
2323
+ return [
2324
+ ...prev,
2325
+ appendToolCall({
2326
+ id: newId,
2327
+ role: "assistant",
2328
+ content: "",
2329
+ timestamp: nowTs(),
2330
+ isStreaming: true,
2331
+ isComplete: false,
2332
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2333
+ })
2334
+ ];
2335
+ })()
2336
+ );
2337
+ hasStreamProgress = true;
2338
+ return;
2339
+ }
2340
+ const sm = await toServerMessageFromEvent(event);
2341
+ if (sm) {
2342
+ const viewMsg = convertServerMessage(sm, {
2343
+ senderOptions: senderOptionsRef.current,
2344
+ createId: generateId,
2345
+ now: nowTs
2346
+ });
2347
+ finalizeCurrentAssistantBubble();
2348
+ setMessages((prev) => [...prev, viewMsg]);
2349
+ return;
2350
+ }
2351
+ handleStreamMessageEvent(event);
2352
+ },
2353
+ onAssetEvent: async (payload) => {
2354
+ const intercepted = applyEventInterceptor({
2355
+ type: "ASSET_CREATED",
2356
+ payload
2357
+ });
2358
+ if (intercepted?.handled) {
2359
+ return;
2360
+ }
2361
+ await (async () => {
2362
+ if (!hasStreamProgress) return;
2363
+ handleStreamAssetEvent(payload, currentAssistantId);
2364
+ })();
2365
+ },
2366
+ signal: abortController.signal
2096
2367
  });
2097
- matched = next.matched;
2098
- return next.matched ? next.messages : prev;
2099
- });
2100
- if (!matched) {
2101
- liveToolUpdatesRef.current.push(update);
2368
+ } finally {
2369
+ setIsStreaming(false);
2370
+ setMessages((prev) => {
2371
+ const hasStreaming = prev.some((msg) => msg.isStreaming);
2372
+ if (!hasStreaming) return prev;
2373
+ return prev.map((msg) => msg.isStreaming ? closeAssistantMessage(msg) : msg);
2374
+ });
2375
+ abortControllerRef.current = null;
2102
2376
  }
2103
- };
2104
- const finalizeActiveAssistantTurn = (finalAnswer) => {
2105
- setMessages((prev) => {
2106
- const currentIdx = prev.findIndex((message2) => message2.id === currentAssistantId && message2.role === "assistant");
2107
- const fallbackIdx = currentIdx >= 0 ? currentIdx : (() => {
2108
- for (let i = prev.length - 1; i >= 0; i--) {
2109
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
2110
- return i;
2111
- }
2112
- }
2113
- return -1;
2114
- })();
2115
- if (fallbackIdx < 0) return prev;
2116
- const message = prev[fallbackIdx];
2117
- const nextMessage = finalizeAssistantMessage(message, finalAnswer);
2118
- if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity) {
2119
- return prev;
2377
+ return currentAssistantId;
2378
+ },
2379
+ [applyEventInterceptor, handleStreamMessageEvent, handleStreamAssetEvent, getRequestHeaders]
2380
+ );
2381
+ const handleSendMessage = useCallback2(
2382
+ async (content, attachments = []) => {
2383
+ if (!content.trim() && attachments.length === 0) return;
2384
+ if (!userId) return;
2385
+ const timestamp = nowTs();
2386
+ const curThreadId = currentThreadIdRef.current;
2387
+ const curThreadExtId = currentThreadExternalIdRef.current;
2388
+ const existingThreadId = curThreadId ?? void 0;
2389
+ const extMap = threadExternalIdMapRef.current;
2390
+ const isPlaceholderThread = existingThreadId ? extMap[existingThreadId] === existingThreadId : false;
2391
+ const threadIdForSend = isPlaceholderThread ? void 0 : existingThreadId;
2392
+ let effectiveThreadExternalId = curThreadExtId ?? (isPlaceholderThread ? existingThreadId : void 0);
2393
+ if (!threadIdForSend) {
2394
+ if (!effectiveThreadExternalId) {
2395
+ effectiveThreadExternalId = generateId();
2120
2396
  }
2121
- const updated = [...prev];
2122
- updated[fallbackIdx] = nextMessage;
2123
- currentAssistantId = nextMessage.id;
2124
- return updated;
2125
- });
2126
- };
2127
- const toServerMessageFromEvent = async (event) => {
2128
- if (!event) return null;
2129
- const type = event?.type || "";
2130
- const payload = event?.payload ?? event;
2131
- if (type === "TOOL_CALL") {
2132
- const parsedToolCall = extractLiveToolCall(payload);
2133
- return {
2134
- id: generateId(),
2135
- threadId: curThreadId ?? "",
2136
- senderType: "tool",
2137
- content: "",
2138
- toolCalls: [{
2139
- id: parsedToolCall.id ?? generateId(),
2140
- name: parsedToolCall.name,
2141
- args: parsedToolCall.arguments,
2142
- ...parsedToolCall.result !== void 0 ? { output: parsedToolCall.result } : {},
2143
- status: parsedToolCall.status
2144
- }]
2145
- };
2397
+ setCurrentThreadExternalId(effectiveThreadExternalId);
2398
+ } else if (curThreadExtId !== (effectiveThreadExternalId ?? null)) {
2399
+ setCurrentThreadExternalId(effectiveThreadExternalId ?? null);
2146
2400
  }
2147
- return null;
2148
- };
2149
- const abortController = new AbortController();
2150
- abortControllerRef.current?.abort();
2151
- abortControllerRef.current = abortController;
2152
- setIsStreaming(true);
2153
- liveToolUpdatesRef.current = [];
2154
- try {
2155
- const normalizedUserMetadata = params.userMetadata ? JSON.parse(JSON.stringify(params.userMetadata)) : void 0;
2156
- const contextSeed = userContextSeedRef.current;
2157
- const contextMetadata = contextSeed ? JSON.parse(JSON.stringify(contextSeed)) : void 0;
2158
- const requestContent = params.content && params.content.length > 0 ? params.content : "";
2159
- const metadataKey = params.threadId ?? params.threadExternalId ?? void 0;
2160
- const currentThreadMetadataMap = threadMetadataMapRef.current;
2161
- const messageMetadata = metadataKey ? currentThreadMetadataMap[metadataKey]?.userContext : void 0;
2162
- const threadMetadata = metadataKey ? currentThreadMetadataMap[metadataKey] : void 0;
2163
- const mergedMetadata = {
2164
- ...messageMetadata ?? {},
2165
- ...params.metadata ?? {}
2401
+ const conversationKey = threadIdForSend ?? effectiveThreadExternalId;
2402
+ const currentMetadata = threadMetadataMapRef.current[conversationKey];
2403
+ const pendingTitle = currentMetadata?.pendingTitle;
2404
+ const userMessage = {
2405
+ id: generateId(),
2406
+ role: "user",
2407
+ content,
2408
+ timestamp,
2409
+ attachments: attachments.length > 0 ? attachments : void 0,
2410
+ isComplete: true,
2411
+ sender: resolveUserSender({
2412
+ id: userId,
2413
+ name: getCurrentUserDisplayName(userName, userId)
2414
+ })
2166
2415
  };
2167
- const finalMetadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
2168
- await runCopilotzStream({
2169
- threadId: params.threadId ?? void 0,
2170
- threadExternalId: params.threadExternalId ?? void 0,
2171
- content: requestContent,
2172
- user: {
2173
- externalId: params.userId,
2174
- name: params.userName ?? params.userId,
2175
- metadata: {
2176
- ...contextMetadata ? contextMetadata : {},
2177
- ...normalizedUserMetadata ?? {}
2178
- }
2416
+ const assistantSender = targetAgentNameRef.current ? resolveAgentSender(
2417
+ {
2418
+ id: targetAgentNameRef.current,
2419
+ name: targetAgentNameRef.current
2179
2420
  },
2180
- attachments: params.attachments,
2181
- metadata: finalMetadata,
2182
- threadMetadata: params.threadMetadata ?? threadMetadata,
2183
- toolCalls: params.toolCalls,
2184
- selectedAgent: params.agentName ?? preferredAgentRef.current ?? null,
2185
- participants: participantsRef.current,
2186
- targetAgent: targetAgentNameRef.current,
2187
- getRequestHeaders,
2188
- onToken: (token, _isComplete, raw, opts) => updateStreamingMessage(token, {
2189
- ...opts,
2190
- agent: raw?.payload?.agent ?? raw?.agent ?? null
2191
- }),
2192
- onMessageEvent: async (event) => {
2193
- const intercepted = applyEventInterceptor(event);
2194
- if (intercepted?.handled) {
2195
- return;
2196
- }
2197
- const type = event?.type || "";
2198
- const payload = getEventPayload(event);
2199
- if (type === "TOOL_RESULT") {
2200
- processToolOutput(payload ?? {});
2201
- applyLiveToolResultUpdate(extractLiveToolResultUpdate(
2202
- payload ?? {}
2203
- ));
2204
- return;
2205
- }
2206
- if (type === "LLM_RESULT") {
2207
- const finalAnswer = typeof payload?.answer === "string" ? payload.answer : void 0;
2208
- finalizeActiveAssistantTurn(finalAnswer);
2209
- return;
2421
+ senderOptionsRef.current
2422
+ ) : preferredAgentRef.current ? resolveAgentSender({ id: preferredAgentRef.current, name: preferredAgentRef.current }, senderOptionsRef.current) : resolveAssistantFallbackSender(senderOptionsRef.current);
2423
+ const assistantPlaceholder = {
2424
+ id: generateId(),
2425
+ role: "assistant",
2426
+ content: "",
2427
+ timestamp: timestamp + 1,
2428
+ isStreaming: true,
2429
+ isComplete: false,
2430
+ sender: assistantSender,
2431
+ activity: createPendingAssistantActivity()
2432
+ };
2433
+ setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);
2434
+ setSpecialState(null);
2435
+ if (!threadsRef.current.some((t) => t.id === conversationKey)) {
2436
+ const newThread = {
2437
+ id: conversationKey,
2438
+ title: content.slice(0, 40) || "Nova conversa",
2439
+ createdAt: timestamp,
2440
+ updatedAt: timestamp,
2441
+ messageCount: 0
2442
+ };
2443
+ setThreads((prev) => [newThread, ...prev]);
2444
+ setThreadMetadataMap((prev) => ({ ...prev, [conversationKey]: {} }));
2445
+ setThreadExternalIdMap((prev) => ({
2446
+ ...prev,
2447
+ [conversationKey]: effectiveThreadExternalId ?? null
2448
+ }));
2449
+ }
2450
+ try {
2451
+ await sendCopilotzMessage({
2452
+ threadId: threadIdForSend,
2453
+ threadExternalId: effectiveThreadExternalId,
2454
+ content,
2455
+ attachments,
2456
+ userId,
2457
+ userName: getCurrentUserDisplayName(userName, userId),
2458
+ agentName: preferredAgentRef.current,
2459
+ assistantMessageId: assistantPlaceholder.id,
2460
+ assistantSender,
2461
+ // Include pending title for new threads
2462
+ threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
2463
+ });
2464
+ await new Promise((r) => setTimeout(r, 1e3));
2465
+ await fetchAndSetThreadsState(userId, effectiveThreadExternalId ?? existingThreadId ?? null);
2466
+ } catch (error) {
2467
+ if (isAbortError(error)) return;
2468
+ console.error("Error sending Copilotz message", error);
2469
+ const nextSpecialState = getSpecialStateFromError(error);
2470
+ if (nextSpecialState) {
2471
+ setSpecialState(nextSpecialState);
2472
+ setMessages((prev) => prev.filter((msg) => !msg.isStreaming));
2473
+ return;
2474
+ }
2475
+ setMessages((prev) => {
2476
+ const finalized = prev.map((msg) => msg.isStreaming ? closeAssistantMessage(msg) : msg);
2477
+ if (finalized.some(hasVisibleAssistantOutput)) {
2478
+ return finalized;
2210
2479
  }
2211
- if (type === "MESSAGE" || type === "NEW_MESSAGE") {
2212
- return;
2480
+ for (let i = finalized.length - 1; i >= 0; i--) {
2481
+ const message = finalized[i];
2482
+ if (message.role !== "assistant") continue;
2483
+ const updated = [...finalized];
2484
+ updated[i] = {
2485
+ ...message,
2486
+ content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2487
+ isStreaming: false,
2488
+ isComplete: true,
2489
+ sender: message.sender ?? resolveAssistantFallbackSender(senderOptionsRef.current)
2490
+ };
2491
+ return updated;
2213
2492
  }
2214
- if (type === "TOOL_CALL") {
2215
- const parsedToolCall = extractLiveToolCall(
2216
- payload ?? {}
2217
- );
2218
- const eventSender = resolveLiveEventSender(event, senderOptionsRef.current);
2219
- currentAssistantSender = eventSender;
2220
- const eventAgentKey = currentAssistantSender.agentId ?? currentAssistantSender.id;
2221
- const callId = parsedToolCall.id ?? generateId();
2222
- const toolName = parsedToolCall.name;
2223
- const bufferedUpdates = liveToolUpdatesRef.current;
2224
- const matchingUpdateIndex = bufferedUpdates.findIndex((upd) => matchesToolResultUpdate({ id: callId, name: toolName }, upd));
2225
- const bufferedUpdate = matchingUpdateIndex >= 0 ? bufferedUpdates[matchingUpdateIndex] : void 0;
2226
- if (matchingUpdateIndex >= 0) {
2227
- bufferedUpdates.splice(matchingUpdateIndex, 1);
2493
+ return [
2494
+ ...finalized,
2495
+ {
2496
+ id: generateId(),
2497
+ role: "assistant",
2498
+ content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2499
+ timestamp: nowTs(),
2500
+ isStreaming: false,
2501
+ isComplete: true,
2502
+ sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2228
2503
  }
2229
- const initialStatus = bufferedUpdate ? bufferedUpdate.status : parsedToolCall.status;
2230
- const initialResult = bufferedUpdate && bufferedUpdate.result !== void 0 ? bufferedUpdate.result : parsedToolCall.result;
2231
- const endTime = bufferedUpdate?.endTime;
2232
- setMessages(
2233
- (prev) => (() => {
2234
- const canHostActivity = (message) => {
2235
- if (!message) return false;
2236
- return message.role === "assistant" && message.isStreaming && message.content.trim().length === 0 && !message.attachments?.length;
2237
- };
2238
- const appendToolCall = (msg) => ({
2239
- ...appendAssistantToolCall(msg, {
2240
- id: callId,
2241
- name: toolName,
2242
- arguments: parsedToolCall.arguments,
2243
- ...initialResult !== void 0 ? { result: initialResult } : {},
2244
- status: initialStatus,
2245
- startTime: Date.now(),
2246
- ...endTime !== void 0 ? { endTime } : {}
2247
- })
2248
- });
2249
- const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming && canHostActivity(message));
2250
- if (currentIdx >= 0) {
2251
- const next = [...prev];
2252
- next[currentIdx] = appendToolCall({
2253
- ...next[currentIdx],
2254
- isStreaming: true,
2255
- isComplete: false,
2256
- ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2257
- });
2258
- return next;
2259
- }
2260
- const last = prev[prev.length - 1];
2261
- if (canHostActivity(last) && canAttachToStreamingAssistant(
2262
- last,
2263
- eventAgentKey
2264
- )) {
2265
- currentAssistantId = last.id;
2266
- const next = [...prev];
2267
- next[prev.length - 1] = appendToolCall({
2268
- ...last,
2269
- isStreaming: true,
2270
- isComplete: false,
2271
- ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2272
- });
2273
- return next;
2274
- }
2275
- const newId = generateId();
2276
- currentAssistantId = newId;
2277
- return [
2278
- ...prev,
2279
- appendToolCall({
2280
- id: newId,
2281
- role: "assistant",
2282
- content: "",
2283
- timestamp: nowTs(),
2284
- isStreaming: true,
2285
- isComplete: false,
2286
- ...currentAssistantSender ? { sender: currentAssistantSender } : {}
2287
- })
2288
- ];
2289
- })()
2290
- );
2291
- hasStreamProgress = true;
2292
- return;
2293
- }
2294
- const sm = await toServerMessageFromEvent(event);
2295
- if (sm) {
2296
- const viewMsg = convertServerMessage(sm, {
2297
- senderOptions: senderOptionsRef.current,
2298
- createId: generateId,
2299
- now: nowTs
2300
- });
2301
- finalizeCurrentAssistantBubble();
2302
- setMessages((prev) => [...prev, viewMsg]);
2303
- return;
2304
- }
2305
- handleStreamMessageEvent(event);
2306
- },
2307
- onAssetEvent: async (payload) => {
2308
- const intercepted = applyEventInterceptor({ type: "ASSET_CREATED", payload });
2309
- if (intercepted?.handled) {
2310
- return;
2311
- }
2312
- await (async () => {
2313
- if (!hasStreamProgress) return;
2314
- handleStreamAssetEvent(payload, currentAssistantId);
2315
- })();
2316
- },
2317
- signal: abortController.signal
2318
- });
2319
- } finally {
2320
- setIsStreaming(false);
2321
- setMessages((prev) => {
2322
- const hasStreaming = prev.some((msg) => msg.isStreaming);
2323
- if (!hasStreaming) return prev;
2324
- return prev.map((msg) => msg.isStreaming ? closeAssistantMessage(msg) : msg);
2325
- });
2326
- abortControllerRef.current = null;
2327
- }
2328
- return currentAssistantId;
2329
- }, [applyEventInterceptor, handleStreamMessageEvent, handleStreamAssetEvent, getRequestHeaders]);
2330
- const handleSendMessage = useCallback2(async (content, attachments = []) => {
2331
- if (!content.trim() && attachments.length === 0) return;
2332
- if (!userId) return;
2333
- const timestamp = nowTs();
2334
- const curThreadId = currentThreadIdRef.current;
2335
- const curThreadExtId = currentThreadExternalIdRef.current;
2336
- const existingThreadId = curThreadId ?? void 0;
2337
- const extMap = threadExternalIdMapRef.current;
2338
- const isPlaceholderThread = existingThreadId ? extMap[existingThreadId] === existingThreadId : false;
2339
- const threadIdForSend = isPlaceholderThread ? void 0 : existingThreadId;
2340
- let effectiveThreadExternalId = curThreadExtId ?? (isPlaceholderThread ? existingThreadId : void 0);
2341
- if (!threadIdForSend) {
2342
- if (!effectiveThreadExternalId) {
2343
- effectiveThreadExternalId = generateId();
2344
- }
2345
- setCurrentThreadExternalId(effectiveThreadExternalId);
2346
- } else if (curThreadExtId !== (effectiveThreadExternalId ?? null)) {
2347
- setCurrentThreadExternalId(effectiveThreadExternalId ?? null);
2348
- }
2349
- const conversationKey = threadIdForSend ?? effectiveThreadExternalId;
2350
- const currentMetadata = threadMetadataMapRef.current[conversationKey];
2351
- const pendingTitle = currentMetadata?.pendingTitle;
2352
- const userMessage = {
2353
- id: generateId(),
2354
- role: "user",
2355
- content,
2356
- timestamp,
2357
- attachments: attachments.length > 0 ? attachments : void 0,
2358
- isComplete: true,
2359
- sender: resolveUserSender({
2360
- id: userId,
2361
- name: userContextSeedRef.current?.profile?.full_name ?? userId
2362
- })
2363
- };
2364
- const assistantSender = targetAgentNameRef.current ? resolveAgentSender(
2365
- { id: targetAgentNameRef.current, name: targetAgentNameRef.current },
2366
- senderOptionsRef.current
2367
- ) : preferredAgentRef.current ? resolveAgentSender(
2368
- { id: preferredAgentRef.current, name: preferredAgentRef.current },
2369
- senderOptionsRef.current
2370
- ) : resolveAssistantFallbackSender(senderOptionsRef.current);
2371
- const assistantPlaceholder = {
2372
- id: generateId(),
2373
- role: "assistant",
2374
- content: "",
2375
- timestamp: timestamp + 1,
2376
- isStreaming: true,
2377
- isComplete: false,
2378
- sender: assistantSender,
2379
- activity: createPendingAssistantActivity()
2380
- };
2381
- setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);
2382
- setSpecialState(null);
2383
- if (!threadsRef.current.some((t) => t.id === conversationKey)) {
2384
- const newThread = {
2385
- id: conversationKey,
2386
- title: content.slice(0, 40) || "Nova conversa",
2387
- createdAt: timestamp,
2388
- updatedAt: timestamp,
2389
- messageCount: 0
2390
- };
2391
- setThreads((prev) => [newThread, ...prev]);
2392
- setThreadMetadataMap((prev) => ({ ...prev, [conversationKey]: {} }));
2393
- setThreadExternalIdMap((prev) => ({ ...prev, [conversationKey]: effectiveThreadExternalId ?? null }));
2394
- }
2395
- try {
2396
- await sendCopilotzMessage({
2397
- threadId: threadIdForSend,
2398
- threadExternalId: effectiveThreadExternalId,
2399
- content,
2400
- attachments,
2401
- userId,
2402
- // userName can be anything, but let's try to find it in context or just fallback
2403
- userName: userContextSeedRef.current?.profile?.full_name ?? userId,
2404
- agentName: preferredAgentRef.current,
2405
- assistantMessageId: assistantPlaceholder.id,
2406
- assistantSender,
2407
- // Include pending title for new threads
2408
- threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
2409
- });
2410
- await new Promise((r) => setTimeout(r, 1e3));
2411
- await fetchAndSetThreadsState(userId, effectiveThreadExternalId ?? existingThreadId ?? null);
2412
- } catch (error) {
2413
- if (isAbortError(error)) return;
2414
- console.error("Error sending Copilotz message", error);
2415
- const nextSpecialState = getSpecialStateFromError(error);
2416
- if (nextSpecialState) {
2417
- setSpecialState(nextSpecialState);
2418
- setMessages((prev) => prev.filter((msg) => !msg.isStreaming));
2419
- return;
2504
+ ];
2505
+ });
2420
2506
  }
2421
- setMessages((prev) => {
2422
- const finalized = prev.map((msg) => msg.isStreaming ? closeAssistantMessage(msg) : msg);
2423
- if (finalized.some(hasVisibleAssistantOutput)) {
2424
- return finalized;
2507
+ },
2508
+ [userId, fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, getSpecialStateFromError]
2509
+ );
2510
+ const bootstrapConversation = useCallback2(
2511
+ async (uid) => {
2512
+ if (!bootstrap?.initialToolCalls && !bootstrap?.initialMessage) return;
2513
+ const bootstrapThreadExternalId = generateId();
2514
+ setCurrentThreadId(bootstrapThreadExternalId);
2515
+ setCurrentThreadExternalId(bootstrapThreadExternalId);
2516
+ setThreadExternalIdMap((prev) => ({
2517
+ ...prev,
2518
+ [bootstrapThreadExternalId]: bootstrapThreadExternalId
2519
+ }));
2520
+ setThreadMetadataMap((prev) => ({
2521
+ ...prev,
2522
+ [bootstrapThreadExternalId]: {}
2523
+ }));
2524
+ const assistantSender = preferredAgentRef.current ? resolveAgentSender({ id: preferredAgentRef.current, name: preferredAgentRef.current }, senderOptionsRef.current) : resolveAssistantFallbackSender(senderOptionsRef.current);
2525
+ const assistantMessageId = generateId();
2526
+ setMessages([
2527
+ {
2528
+ id: assistantMessageId,
2529
+ role: "assistant",
2530
+ content: "",
2531
+ timestamp: nowTs(),
2532
+ isStreaming: true,
2533
+ isComplete: false,
2534
+ sender: assistantSender,
2535
+ activity: createPendingAssistantActivity()
2425
2536
  }
2426
- for (let i = finalized.length - 1; i >= 0; i--) {
2427
- const message = finalized[i];
2428
- if (message.role !== "assistant") continue;
2429
- const updated = [...finalized];
2430
- updated[i] = {
2431
- ...message,
2432
- content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2433
- isStreaming: false,
2434
- isComplete: true,
2435
- sender: message.sender ?? resolveAssistantFallbackSender(senderOptionsRef.current)
2436
- };
2437
- return updated;
2537
+ ]);
2538
+ setMessagePageInfo(createEmptyMessagePageInfo());
2539
+ persistedToolUpdatesRef.current = [];
2540
+ setSpecialState(null);
2541
+ try {
2542
+ await sendCopilotzMessage({
2543
+ threadExternalId: bootstrapThreadExternalId,
2544
+ content: bootstrap.initialMessage || "",
2545
+ toolCalls: bootstrap.initialToolCalls,
2546
+ userId: uid,
2547
+ userName: getCurrentUserDisplayName(userName, uid),
2548
+ agentName: preferredAgentRef.current,
2549
+ assistantMessageId,
2550
+ assistantSender,
2551
+ threadMetadata: {
2552
+ name: defaultThreadName || "Main Thread"
2553
+ }
2554
+ });
2555
+ await new Promise((r) => setTimeout(r, 1e3));
2556
+ await fetchAndSetThreadsState(uid, bootstrapThreadExternalId);
2557
+ } catch (error) {
2558
+ if (isAbortError(error)) return;
2559
+ console.error("Error bootstrapping conversation", error);
2560
+ const nextSpecialState = getSpecialStateFromError(error);
2561
+ if (nextSpecialState) {
2562
+ setSpecialState(nextSpecialState);
2563
+ setMessages([]);
2564
+ return;
2438
2565
  }
2439
- return [
2440
- ...finalized,
2566
+ setMessages([
2441
2567
  {
2442
2568
  id: generateId(),
2443
2569
  role: "assistant",
2444
- content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2570
+ content: "N\xE3o foi poss\xEDvel iniciar a conversa. Tente novamente mais tarde.",
2445
2571
  timestamp: nowTs(),
2446
2572
  isStreaming: false,
2447
2573
  isComplete: true,
2448
2574
  sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2449
2575
  }
2450
- ];
2451
- });
2452
- }
2453
- }, [userId, fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, getSpecialStateFromError]);
2454
- const bootstrapConversation = useCallback2(async (uid) => {
2455
- if (!bootstrap?.initialToolCalls && !bootstrap?.initialMessage) return;
2456
- const bootstrapThreadExternalId = generateId();
2457
- setCurrentThreadId(bootstrapThreadExternalId);
2458
- setCurrentThreadExternalId(bootstrapThreadExternalId);
2459
- setThreadExternalIdMap((prev) => ({ ...prev, [bootstrapThreadExternalId]: bootstrapThreadExternalId }));
2460
- setThreadMetadataMap((prev) => ({ ...prev, [bootstrapThreadExternalId]: {} }));
2461
- const assistantSender = preferredAgentRef.current ? resolveAgentSender(
2462
- { id: preferredAgentRef.current, name: preferredAgentRef.current },
2463
- senderOptionsRef.current
2464
- ) : resolveAssistantFallbackSender(senderOptionsRef.current);
2465
- const assistantMessageId = generateId();
2466
- setMessages([{
2467
- id: assistantMessageId,
2468
- role: "assistant",
2469
- content: "",
2470
- timestamp: nowTs(),
2471
- isStreaming: true,
2472
- isComplete: false,
2473
- sender: assistantSender,
2474
- activity: createPendingAssistantActivity()
2475
- }]);
2476
- setMessagePageInfo(createEmptyMessagePageInfo());
2477
- persistedToolUpdatesRef.current = [];
2478
- setSpecialState(null);
2479
- try {
2480
- await sendCopilotzMessage({
2481
- threadExternalId: bootstrapThreadExternalId,
2482
- content: bootstrap.initialMessage || "",
2483
- toolCalls: bootstrap.initialToolCalls,
2484
- userId: uid,
2485
- agentName: preferredAgentRef.current,
2486
- assistantMessageId,
2487
- assistantSender,
2488
- threadMetadata: {
2489
- name: defaultThreadName || "Main Thread"
2490
- }
2491
- });
2492
- await new Promise((r) => setTimeout(r, 1e3));
2493
- await fetchAndSetThreadsState(uid, bootstrapThreadExternalId);
2494
- } catch (error) {
2495
- if (isAbortError(error)) return;
2496
- console.error("Error bootstrapping conversation", error);
2497
- const nextSpecialState = getSpecialStateFromError(error);
2498
- if (nextSpecialState) {
2499
- setSpecialState(nextSpecialState);
2500
- setMessages([]);
2501
- return;
2576
+ ]);
2502
2577
  }
2503
- setMessages([
2504
- {
2505
- id: generateId(),
2506
- role: "assistant",
2507
- content: "N\xE3o foi poss\xEDvel iniciar a conversa. Tente novamente mais tarde.",
2508
- timestamp: nowTs(),
2509
- isStreaming: false,
2510
- isComplete: true,
2511
- sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2512
- }
2513
- ]);
2514
- }
2515
- }, [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName, getSpecialStateFromError]);
2578
+ },
2579
+ [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName, getSpecialStateFromError]
2580
+ );
2516
2581
  const reset = useCallback2(() => {
2517
2582
  messagesRequestRef.current += 1;
2518
2583
  setThreads([]);
@@ -2561,7 +2626,10 @@ function useCopilotz({
2561
2626
  const metadata = threadMetadataMap[currentThreadId];
2562
2627
  if (!metadata) return;
2563
2628
  if (metadata.userContext && typeof metadata.userContext === "object") {
2564
- setUserContextSeed((prev) => ({ ...prev, ...metadata.userContext }));
2629
+ setUserContextSeed((prev) => ({
2630
+ ...prev,
2631
+ ...metadata.userContext
2632
+ }));
2565
2633
  }
2566
2634
  }, [currentThreadId, threadMetadataMap]);
2567
2635
  return {
@@ -2580,6 +2648,7 @@ function useCopilotz({
2580
2648
  selectThread: handleSelectThread,
2581
2649
  renameThread: handleRenameThread,
2582
2650
  archiveThread: handleArchiveThread,
2651
+ updateThreadTags: handleUpdateThreadTags,
2583
2652
  deleteThread: handleDeleteThread,
2584
2653
  stopGeneration: handleStop,
2585
2654
  fetchAndSetThreadsState,
@@ -2591,39 +2660,7 @@ function useCopilotz({
2591
2660
 
2592
2661
  // src/CopilotzChat.tsx
2593
2662
  import { jsx } from "react/jsx-runtime";
2594
- var CopilotzChat = ({
2595
- userId,
2596
- userName,
2597
- userAvatar,
2598
- userEmail,
2599
- initialContext,
2600
- bootstrap,
2601
- config: userConfig,
2602
- callbacks: userCallbacks,
2603
- customComponent,
2604
- onToolOutput,
2605
- onCurrentThreadIdChange,
2606
- onLogout,
2607
- onViewProfile,
2608
- onAddMemory,
2609
- onUpdateMemory,
2610
- onDeleteMemory,
2611
- userMenuSections,
2612
- userMenuAdditionalItems,
2613
- suggestions,
2614
- agentOptions = [],
2615
- selectedAgentId = null,
2616
- onSelectAgent,
2617
- participantIds,
2618
- onParticipantsChange,
2619
- targetAgentId = null,
2620
- onTargetAgentChange,
2621
- getRequestHeaders,
2622
- className,
2623
- eventInterceptor,
2624
- runErrorInterceptor,
2625
- renderSpecialState
2626
- }) => {
2663
+ var CopilotzChat = ({ userId, userName, userAvatar, userEmail, initialContext, bootstrap, config: userConfig, callbacks: userCallbacks, customComponent, onToolOutput, onCurrentThreadIdChange, onLogout, onViewProfile, onAddMemory, onUpdateMemory, onDeleteMemory, userMenuSections, userMenuAdditionalItems, suggestions, agentOptions = [], selectedAgentId = null, onSelectAgent, participantIds, onParticipantsChange, targetAgentId = null, onTargetAgentChange, getRequestHeaders, className, eventInterceptor, runErrorInterceptor, renderSpecialState }) => {
2627
2664
  const selectedAgent = agentOptions.find((agent) => agent.id === selectedAgentId) || null;
2628
2665
  const participantAgentIds = useMemo(() => {
2629
2666
  if (!participantIds || participantIds.length === 0) return null;
@@ -2634,26 +2671,7 @@ var CopilotzChat = ({
2634
2671
  if (!targetAgentId) return null;
2635
2672
  return targetAgentId;
2636
2673
  }, [targetAgentId]);
2637
- const {
2638
- messages,
2639
- isMessagesLoading,
2640
- isLoadingOlderMessages,
2641
- messagePageInfo,
2642
- threads,
2643
- currentThreadId,
2644
- isStreaming,
2645
- specialState,
2646
- clearSpecialState,
2647
- userContextSeed,
2648
- sendMessage,
2649
- createThread,
2650
- selectThread,
2651
- renameThread,
2652
- archiveThread,
2653
- deleteThread: deleteThread2,
2654
- stopGeneration,
2655
- loadOlderMessages
2656
- } = useCopilotz({
2674
+ const { messages, isMessagesLoading, isLoadingOlderMessages, messagePageInfo, threads, currentThreadId, isStreaming, specialState, clearSpecialState, userContextSeed, sendMessage, createThread, selectThread, renameThread, archiveThread, updateThreadTags, deleteThread: deleteThread2, stopGeneration, loadOlderMessages } = useCopilotz({
2657
2675
  userId,
2658
2676
  userName,
2659
2677
  userAvatar,
@@ -2674,17 +2692,7 @@ var CopilotzChat = ({
2674
2692
  onCurrentThreadIdChange?.(currentThreadId);
2675
2693
  }, [currentThreadId, onCurrentThreadIdChange]);
2676
2694
  const chatCallbacks = useMemo(() => {
2677
- const {
2678
- onSendMessage: _1,
2679
- onStopGeneration: _2,
2680
- onCreateThread: _3,
2681
- onSelectThread: _4,
2682
- onRenameThread: _5,
2683
- onArchiveThread: _6,
2684
- onDeleteThread: _7,
2685
- onCopyMessage: _8,
2686
- ...restUserCallbacks
2687
- } = userCallbacks || {};
2695
+ const { onSendMessage: _1, onStopGeneration: _2, onCreateThread: _3, onSelectThread: _4, onRenameThread: _5, onArchiveThread: _6, onDeleteThread: _7, onUpdateThreadTags: _8, onCopyMessage: _9, ...restUserCallbacks } = userCallbacks || {};
2688
2696
  return {
2689
2697
  ...restUserCallbacks,
2690
2698
  onSendMessage: (content, attachments) => {
@@ -2711,6 +2719,10 @@ var CopilotzChat = ({
2711
2719
  void archiveThread(threadId);
2712
2720
  userCallbacks?.onArchiveThread?.(threadId);
2713
2721
  },
2722
+ onUpdateThreadTags: (threadId, tags) => {
2723
+ void updateThreadTags(threadId, tags);
2724
+ userCallbacks?.onUpdateThreadTags?.(threadId, tags);
2725
+ },
2714
2726
  onDeleteThread: (threadId) => {
2715
2727
  void deleteThread2(threadId);
2716
2728
  userCallbacks?.onDeleteThread?.(threadId);
@@ -2726,18 +2738,7 @@ var CopilotzChat = ({
2726
2738
  onLogout,
2727
2739
  onViewProfile
2728
2740
  };
2729
- }, [
2730
- sendMessage,
2731
- stopGeneration,
2732
- createThread,
2733
- selectThread,
2734
- renameThread,
2735
- archiveThread,
2736
- deleteThread2,
2737
- userCallbacks,
2738
- onLogout,
2739
- onViewProfile
2740
- ]);
2741
+ }, [sendMessage, stopGeneration, createThread, selectThread, renameThread, archiveThread, updateThreadTags, deleteThread2, userCallbacks, onLogout, onViewProfile]);
2741
2742
  const mergedConfig = useMemo(() => {
2742
2743
  const base = userConfig || {};
2743
2744
  if (!customComponent) {
@@ -2754,53 +2755,25 @@ var CopilotzChat = ({
2754
2755
  }, [userConfig, customComponent]);
2755
2756
  const effectiveUserName = userName || userId;
2756
2757
  const effectiveUserAvatar = userAvatar;
2757
- const userProp = useMemo(() => ({
2758
- id: userId,
2759
- name: effectiveUserName,
2760
- email: userEmail,
2761
- avatar: effectiveUserAvatar
2762
- }), [userId, effectiveUserName, userEmail, effectiveUserAvatar]);
2763
- const assistantProp = useMemo(() => ({
2764
- name: userConfig?.branding?.title,
2765
- avatar: userConfig?.branding?.avatar,
2766
- description: userConfig?.branding?.subtitle
2767
- }), [
2768
- userConfig?.branding?.title,
2769
- userConfig?.branding?.avatar,
2770
- userConfig?.branding?.subtitle
2771
- ]);
2758
+ const userProp = useMemo(
2759
+ () => ({
2760
+ id: userId,
2761
+ name: effectiveUserName,
2762
+ email: userEmail,
2763
+ avatar: effectiveUserAvatar
2764
+ }),
2765
+ [userId, effectiveUserName, userEmail, effectiveUserAvatar]
2766
+ );
2767
+ const assistantProp = useMemo(
2768
+ () => ({
2769
+ name: userConfig?.branding?.title,
2770
+ avatar: userConfig?.branding?.avatar,
2771
+ description: userConfig?.branding?.subtitle
2772
+ }),
2773
+ [userConfig?.branding?.title, userConfig?.branding?.avatar, userConfig?.branding?.subtitle]
2774
+ );
2772
2775
  const specialStateContent = specialState ? renderSpecialState?.(specialState, { clear: clearSpecialState }) : null;
2773
- return /* @__PURE__ */ jsx(ChatUserContextProvider, { initial: userContextSeed, children: specialStateContent ?? /* @__PURE__ */ jsx(
2774
- ChatUI,
2775
- {
2776
- messages,
2777
- isMessagesLoading,
2778
- isLoadingOlderMessages,
2779
- hasMoreMessagesBefore: messagePageInfo.hasMoreBefore,
2780
- onLoadOlderMessages: loadOlderMessages,
2781
- threads,
2782
- currentThreadId,
2783
- config: mergedConfig,
2784
- callbacks: chatCallbacks,
2785
- isGenerating: isStreaming,
2786
- suggestions,
2787
- agentOptions,
2788
- selectedAgentId,
2789
- onSelectAgent,
2790
- participantIds,
2791
- onParticipantsChange,
2792
- targetAgentId,
2793
- onTargetAgentChange,
2794
- user: userProp,
2795
- assistant: assistantProp,
2796
- onAddMemory,
2797
- onUpdateMemory,
2798
- onDeleteMemory,
2799
- userMenuSections,
2800
- userMenuAdditionalItems,
2801
- className
2802
- }
2803
- ) });
2776
+ return /* @__PURE__ */ jsx(ChatUserContextProvider, { initial: userContextSeed, children: specialStateContent ?? /* @__PURE__ */ jsx(ChatUI, { messages, isMessagesLoading, isLoadingOlderMessages, hasMoreMessagesBefore: messagePageInfo.hasMoreBefore, onLoadOlderMessages: loadOlderMessages, threads, currentThreadId, config: mergedConfig, callbacks: chatCallbacks, isGenerating: isStreaming, suggestions, agentOptions, selectedAgentId, onSelectAgent, participantIds, onParticipantsChange, targetAgentId, onTargetAgentChange, user: userProp, assistant: assistantProp, onAddMemory, onUpdateMemory, onDeleteMemory, userMenuSections, userMenuAdditionalItems, className }) });
2804
2777
  };
2805
2778
  export {
2806
2779
  CopilotzChat,