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