@copilotz/chat-adapter 0.1.0 → 0.1.2

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
@@ -1,5 +1,5 @@
1
1
  // src/CopilotzChat.tsx
2
- import { useMemo } from "react";
2
+ import { useMemo, useEffect as useEffect3, useState as useState3 } from "react";
3
3
  import { ChatUI, ChatUserContextProvider } from "@copilotz/chat-ui";
4
4
 
5
5
  // ../node_modules/lucide-react/dist/esm/createLucideIcon.js
@@ -99,7 +99,7 @@ var __iconNode = [
99
99
  var User = createLucideIcon("user", __iconNode);
100
100
 
101
101
  // src/useCopilotzChat.ts
102
- import { useState, useCallback, useRef, useEffect } from "react";
102
+ import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect2 } from "react";
103
103
 
104
104
  // src/copilotzService.ts
105
105
  var rawBaseValue = import.meta.env?.VITE_API_URL;
@@ -259,6 +259,7 @@ async function runCopilotzStream(options) {
259
259
  metadata,
260
260
  threadMetadata,
261
261
  toolCalls,
262
+ selectedAgent,
262
263
  onToken,
263
264
  onMessageEvent,
264
265
  onAssetEvent,
@@ -304,7 +305,7 @@ async function runCopilotzStream(options) {
304
305
  id: threadId ?? null,
305
306
  externalId: threadExternalId ?? null,
306
307
  name: threadName,
307
- participants: ["assistant"],
308
+ participants: [selectedAgent || "assistant"],
308
309
  metadata: Object.keys(restThreadMetadata).length > 0 ? restThreadMetadata : null
309
310
  } : void 0;
310
311
  const preparedAudioParts = [];
@@ -577,37 +578,141 @@ async function getAssetDataUrl(refOrId) {
577
578
  return { dataUrl: data.dataUrl, mime: data.mime, assetId: data.assetId };
578
579
  }
579
580
  async function resolveAssetsInMessages(messages) {
580
- const resolved = [];
581
- for (const msg of messages) {
581
+ const inFlightByRef = /* @__PURE__ */ new Map();
582
+ const resolveAssetRef = (assetRef) => {
583
+ if (!inFlightByRef.has(assetRef)) {
584
+ inFlightByRef.set(assetRef, getAssetDataUrl(assetRef));
585
+ }
586
+ return inFlightByRef.get(assetRef);
587
+ };
588
+ return Promise.all(messages.map(async (msg) => {
582
589
  const meta = msg.metadata ?? void 0;
583
590
  const attachments = Array.isArray(meta?.attachments) ? meta.attachments : void 0;
584
591
  if (!attachments || attachments.length === 0) {
585
- resolved.push(msg);
586
- continue;
592
+ return msg;
587
593
  }
588
- const newAttachments = [];
589
- for (const att of attachments) {
594
+ const newAttachments = await Promise.all(attachments.map(async (att) => {
590
595
  const assetRef = typeof att?.assetRef === "string" ? att.assetRef : void 0;
591
- if (assetRef) {
592
- try {
593
- const { dataUrl, mime } = await getAssetDataUrl(assetRef);
594
- const kind = typeof att.kind === "string" ? att.kind : "image";
595
- newAttachments.push({
596
- kind,
597
- dataUrl,
598
- mimeType: typeof att.mimeType === "string" ? att.mimeType : mime ?? void 0
599
- });
600
- } catch {
601
- newAttachments.push(att);
602
- }
603
- } else {
604
- newAttachments.push(att);
596
+ if (!assetRef) return att;
597
+ try {
598
+ const { dataUrl, mime } = await resolveAssetRef(assetRef);
599
+ const kind = typeof att.kind === "string" ? att.kind : "image";
600
+ return {
601
+ kind,
602
+ dataUrl,
603
+ mimeType: typeof att.mimeType === "string" ? att.mimeType : mime ?? void 0
604
+ };
605
+ } catch {
606
+ return att;
605
607
  }
606
- }
608
+ }));
607
609
  const newMeta = { ...meta ?? {}, attachments: newAttachments };
608
- resolved.push({ ...msg, metadata: newMeta });
610
+ return { ...msg, metadata: newMeta };
611
+ }));
612
+ }
613
+
614
+ // src/useUrlState.ts
615
+ import { useState, useEffect, useCallback, useRef } from "react";
616
+ var DEFAULT_PARAMS = {
617
+ thread: "thread",
618
+ agent: "agent",
619
+ prompt: "prompt"
620
+ };
621
+ var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.location !== "undefined";
622
+ var getSearchParams = () => {
623
+ if (!isBrowser) return new URLSearchParams();
624
+ return new URLSearchParams(globalThis.location.search);
625
+ };
626
+ var updateUrl = (params, mode) => {
627
+ if (!isBrowser) return;
628
+ const url = new URL(globalThis.location.href);
629
+ url.search = params.toString();
630
+ if (mode === "push") {
631
+ globalThis.history.pushState({}, "", url.toString());
632
+ } else {
633
+ globalThis.history.replaceState({}, "", url.toString());
609
634
  }
610
- return resolved;
635
+ };
636
+ function useUrlState(config = {}) {
637
+ const {
638
+ enabled = true,
639
+ mode = "replace",
640
+ params: userParams = {},
641
+ clearPromptAfterRead = true
642
+ } = config;
643
+ const params = { ...DEFAULT_PARAMS, ...userParams };
644
+ const isReadOnly = mode === "read-only";
645
+ const updateMode = mode === "read-only" ? "replace" : mode;
646
+ const initialReadDone = useRef(false);
647
+ const promptCleared = useRef(false);
648
+ const parseUrlState = useCallback(() => {
649
+ if (!enabled || !isBrowser) {
650
+ return { threadId: null, agentId: null, prompt: null };
651
+ }
652
+ const searchParams = getSearchParams();
653
+ return {
654
+ threadId: searchParams.get(params.thread) || null,
655
+ agentId: searchParams.get(params.agent) || null,
656
+ prompt: promptCleared.current ? null : searchParams.get(params.prompt) || null
657
+ };
658
+ }, [enabled, params.thread, params.agent, params.prompt]);
659
+ const [state, setState] = useState(parseUrlState);
660
+ useEffect(() => {
661
+ if (!enabled || !isBrowser) return;
662
+ if (!initialReadDone.current) {
663
+ const initialState = parseUrlState();
664
+ setState(initialState);
665
+ initialReadDone.current = true;
666
+ if (clearPromptAfterRead && initialState.prompt && !isReadOnly) {
667
+ const searchParams = getSearchParams();
668
+ searchParams.delete(params.prompt);
669
+ updateUrl(searchParams, "replace");
670
+ promptCleared.current = true;
671
+ }
672
+ }
673
+ const handlePopState = () => {
674
+ setState(parseUrlState());
675
+ };
676
+ globalThis.addEventListener("popstate", handlePopState);
677
+ return () => globalThis.removeEventListener("popstate", handlePopState);
678
+ }, [enabled, parseUrlState, clearPromptAfterRead, params.prompt, isReadOnly]);
679
+ const setThreadId = useCallback((threadId) => {
680
+ if (!enabled || isReadOnly || !isBrowser) return;
681
+ const searchParams = getSearchParams();
682
+ if (threadId) {
683
+ searchParams.set(params.thread, threadId);
684
+ } else {
685
+ searchParams.delete(params.thread);
686
+ }
687
+ updateUrl(searchParams, updateMode);
688
+ setState((prev) => ({ ...prev, threadId }));
689
+ }, [enabled, isReadOnly, params.thread, updateMode]);
690
+ const setAgentId = useCallback((agentId) => {
691
+ if (!enabled || isReadOnly || !isBrowser) return;
692
+ const searchParams = getSearchParams();
693
+ if (agentId) {
694
+ searchParams.set(params.agent, agentId);
695
+ } else {
696
+ searchParams.delete(params.agent);
697
+ }
698
+ updateUrl(searchParams, updateMode);
699
+ setState((prev) => ({ ...prev, agentId }));
700
+ }, [enabled, isReadOnly, params.agent, updateMode]);
701
+ const clearPrompt = useCallback(() => {
702
+ if (!enabled || isReadOnly || !isBrowser) return;
703
+ const searchParams = getSearchParams();
704
+ searchParams.delete(params.prompt);
705
+ updateUrl(searchParams, "replace");
706
+ promptCleared.current = true;
707
+ setState((prev) => ({ ...prev, prompt: null }));
708
+ }, [enabled, isReadOnly, params.prompt]);
709
+ return {
710
+ state,
711
+ setThreadId,
712
+ setAgentId,
713
+ clearPrompt,
714
+ isEnabled: enabled
715
+ };
611
716
  }
612
717
 
613
718
  // src/useCopilotzChat.ts
@@ -667,48 +772,46 @@ var convertServerMessage = (msg) => {
667
772
  toolCalls: hasToolCalls ? mappedToolCalls : void 0
668
773
  };
669
774
  };
670
- function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onToolOutput }) {
671
- const [threads, setThreads] = useState([]);
672
- const [threadMetadataMap, setThreadMetadataMap] = useState({});
673
- const [threadExternalIdMap, setThreadExternalIdMap] = useState({});
674
- const [currentThreadId, setCurrentThreadId] = useState(null);
675
- const [currentThreadExternalId, setCurrentThreadExternalId] = useState(null);
676
- const [messages, setMessages] = useState([]);
677
- const [isStreaming, setIsStreaming] = useState(false);
678
- const [userContextSeed, setUserContextSeed] = useState(initialContext || {});
679
- const threadsRef = useRef(threads);
680
- const threadMetadataMapRef = useRef(threadMetadataMap);
681
- const threadExternalIdMapRef = useRef(threadExternalIdMap);
682
- const currentThreadIdRef = useRef(currentThreadId);
683
- const currentThreadExternalIdRef = useRef(currentThreadExternalId);
684
- const userContextSeedRef = useRef(userContextSeed);
685
- useEffect(() => {
686
- threadsRef.current = threads;
687
- }, [threads]);
688
- useEffect(() => {
689
- threadMetadataMapRef.current = threadMetadataMap;
690
- }, [threadMetadataMap]);
691
- useEffect(() => {
692
- threadExternalIdMapRef.current = threadExternalIdMap;
693
- }, [threadExternalIdMap]);
694
- useEffect(() => {
695
- currentThreadIdRef.current = currentThreadId;
696
- }, [currentThreadId]);
697
- useEffect(() => {
698
- currentThreadExternalIdRef.current = currentThreadExternalId;
699
- }, [currentThreadExternalId]);
700
- useEffect(() => {
701
- userContextSeedRef.current = userContextSeed;
702
- }, [userContextSeed]);
703
- const abortControllerRef = useRef(null);
704
- const messagesRequestRef = useRef(0);
705
- const initializationRef = useRef({ userId: null, started: false });
706
- useEffect(() => {
775
+ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onToolOutput, preferredAgentName, urlSync }) {
776
+ const {
777
+ state: urlState,
778
+ setThreadId: setUrlThreadId,
779
+ setAgentId: setUrlAgentId,
780
+ clearPrompt: clearUrlPrompt,
781
+ isEnabled: isUrlSyncEnabled
782
+ } = useUrlState(urlSync);
783
+ const [threads, setThreads] = useState2([]);
784
+ const [threadMetadataMap, setThreadMetadataMap] = useState2({});
785
+ const [threadExternalIdMap, setThreadExternalIdMap] = useState2({});
786
+ const [currentThreadId, setCurrentThreadId] = useState2(null);
787
+ const [currentThreadExternalId, setCurrentThreadExternalId] = useState2(null);
788
+ const [messages, setMessages] = useState2([]);
789
+ const [isMessagesLoading, setIsMessagesLoading] = useState2(false);
790
+ const [isStreaming, setIsStreaming] = useState2(false);
791
+ const [userContextSeed, setUserContextSeed] = useState2(initialContext || {});
792
+ const preferredAgentRef = useRef2(preferredAgentName ?? null);
793
+ const threadsRef = useRef2(threads);
794
+ const threadMetadataMapRef = useRef2(threadMetadataMap);
795
+ const threadExternalIdMapRef = useRef2(threadExternalIdMap);
796
+ const currentThreadIdRef = useRef2(currentThreadId);
797
+ const currentThreadExternalIdRef = useRef2(currentThreadExternalId);
798
+ const userContextSeedRef = useRef2(userContextSeed);
799
+ threadsRef.current = threads;
800
+ threadMetadataMapRef.current = threadMetadataMap;
801
+ threadExternalIdMapRef.current = threadExternalIdMap;
802
+ currentThreadIdRef.current = currentThreadId;
803
+ currentThreadExternalIdRef.current = currentThreadExternalId;
804
+ userContextSeedRef.current = userContextSeed;
805
+ preferredAgentRef.current = preferredAgentName ?? null;
806
+ const abortControllerRef = useRef2(null);
807
+ const messagesRequestRef = useRef2(0);
808
+ const initializationRef = useRef2({ userId: null, started: false });
809
+ useEffect2(() => {
707
810
  if (initialContext) {
708
811
  setUserContextSeed((prev) => ({ ...prev, ...initialContext }));
709
812
  }
710
813
  }, [initialContext]);
711
- const processToolOutput = useCallback((output) => {
814
+ const processToolOutput = useCallback2((output) => {
712
815
  if (!output) return;
713
816
  const contextPatch = {};
714
817
  if (output.userContext && typeof output.userContext === "object") {
@@ -719,7 +822,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
719
822
  }
720
823
  onToolOutput?.(output);
721
824
  }, [onToolOutput]);
722
- const handleStreamMessageEvent = useCallback((event) => {
825
+ const handleStreamMessageEvent = useCallback2((event) => {
723
826
  const payload = event?.payload;
724
827
  if (!payload) return;
725
828
  if (payload.senderType === "tool") {
@@ -776,7 +879,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
776
879
  });
777
880
  }
778
881
  }, [processToolOutput]);
779
- const updateThreadsState = useCallback((rawThreads, preferredExternalId) => {
882
+ const updateThreadsState = useCallback2((rawThreads, preferredExternalId) => {
780
883
  const metadataMap = {};
781
884
  const externalMap = {};
782
885
  const normalized = rawThreads.map((thread) => {
@@ -818,7 +921,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
818
921
  setCurrentThreadExternalId(nextThreadId ? externalMap[nextThreadId] ?? null : null);
819
922
  return nextThreadId;
820
923
  }, []);
821
- const fetchAndSetThreadsState = useCallback(async (uid, preferredExternalId) => {
924
+ const fetchAndSetThreadsState = useCallback2(async (uid, preferredExternalId) => {
822
925
  try {
823
926
  const rawThreads = await fetchThreads(uid);
824
927
  return updateThreadsState(rawThreads, preferredExternalId);
@@ -828,9 +931,10 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
828
931
  return null;
829
932
  }
830
933
  }, [updateThreadsState]);
831
- const loadThreadMessages = useCallback(async (threadId) => {
832
- const requestId = Date.now();
934
+ const loadThreadMessages = useCallback2(async (threadId) => {
935
+ const requestId = messagesRequestRef.current + 1;
833
936
  messagesRequestRef.current = requestId;
937
+ setIsMessagesLoading(true);
834
938
  try {
835
939
  const rawMessages = await fetchThreadMessages(threadId);
836
940
  const resolvedMessages = await resolveAssetsInMessages(rawMessages);
@@ -857,15 +961,22 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
857
961
  } catch (error) {
858
962
  if (isAbortError(error)) return;
859
963
  console.error(`Error loading messages for thread ${threadId}`, error);
964
+ } finally {
965
+ if (messagesRequestRef.current === requestId) {
966
+ setIsMessagesLoading(false);
967
+ }
860
968
  }
861
969
  }, [processToolOutput]);
862
- const handleSelectThread = useCallback(async (threadId) => {
970
+ const handleSelectThread = useCallback2(async (threadId) => {
863
971
  setCurrentThreadId(threadId);
972
+ setMessages([]);
864
973
  const extMap = threadExternalIdMapRef.current;
865
974
  setCurrentThreadExternalId(extMap[threadId] ?? null);
866
975
  await loadThreadMessages(threadId);
867
976
  }, [loadThreadMessages]);
868
- const handleCreateThread = useCallback((title) => {
977
+ const handleCreateThread = useCallback2((title) => {
978
+ messagesRequestRef.current += 1;
979
+ setIsMessagesLoading(false);
869
980
  const id = generateId();
870
981
  const now = nowTs();
871
982
  const newThread = {
@@ -883,7 +994,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
883
994
  setCurrentThreadExternalId(id);
884
995
  setMessages([]);
885
996
  }, []);
886
- const handleRenameThread = useCallback(async (threadId, newTitle) => {
997
+ const handleRenameThread = useCallback2(async (threadId, newTitle) => {
887
998
  const trimmedTitle = newTitle.trim();
888
999
  if (!trimmedTitle) return;
889
1000
  setThreads(
@@ -907,7 +1018,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
907
1018
  }
908
1019
  }
909
1020
  }, [userId, fetchAndSetThreadsState]);
910
- const handleArchiveThread = useCallback(async (threadId) => {
1021
+ const handleArchiveThread = useCallback2(async (threadId) => {
911
1022
  const thread = threadsRef.current.find((t) => t.id === threadId);
912
1023
  if (!thread) return;
913
1024
  const newArchivedStatus = !thread.isArchived;
@@ -927,7 +1038,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
927
1038
  }
928
1039
  }
929
1040
  }, [userId, fetchAndSetThreadsState]);
930
- const handleDeleteThread = useCallback(async (threadId) => {
1041
+ const handleDeleteThread = useCallback2(async (threadId) => {
931
1042
  const extMap = threadExternalIdMapRef.current;
932
1043
  const isPlaceholder = extMap[threadId] === threadId;
933
1044
  setThreads((prev) => prev.filter((t) => t.id !== threadId));
@@ -964,13 +1075,17 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
964
1075
  }
965
1076
  }
966
1077
  }, [userId, fetchAndSetThreadsState, loadThreadMessages]);
967
- const handleStop = useCallback(() => {
1078
+ const handleStop = useCallback2(() => {
968
1079
  abortControllerRef.current?.abort();
969
1080
  abortControllerRef.current = null;
970
1081
  setIsStreaming(false);
971
- setMessages((prev) => prev.map((msg) => msg.isStreaming ? { ...msg, isStreaming: false, isComplete: true } : msg));
1082
+ setMessages((prev) => {
1083
+ const hasStreaming = prev.some((msg) => msg.isStreaming);
1084
+ if (!hasStreaming) return prev;
1085
+ return prev.map((msg) => msg.isStreaming ? { ...msg, isStreaming: false, isComplete: true } : msg);
1086
+ });
972
1087
  }, []);
973
- const handleStreamAssetEvent = useCallback((payload, assistantMessageId) => {
1088
+ const handleStreamAssetEvent = useCallback2((payload, assistantMessageId) => {
974
1089
  if (!payload?.dataUrl) return;
975
1090
  const mimeType = payload.mime || "image/png";
976
1091
  const dataUrl = payload.dataUrl;
@@ -992,22 +1107,36 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
992
1107
  isComplete: true
993
1108
  } : msg));
994
1109
  }, []);
995
- const sendCopilotzMessage = useCallback(async (params) => {
1110
+ const sendCopilotzMessage = useCallback2(async (params) => {
996
1111
  let currentAssistantId = generateId();
997
1112
  params.onBeforeStart?.(currentAssistantId);
998
1113
  let hasStreamProgress = false;
999
1114
  let pendingStartNewAssistantBubble = false;
1000
- const ensureStreamingBubble = () => {
1115
+ const updateStreamingMessage = (partial, isComplete) => {
1116
+ if (partial && partial.length > 0) {
1117
+ hasStreamProgress = true;
1118
+ }
1001
1119
  setMessages((prev) => {
1002
1120
  const idx = prev.findIndex((m) => m.id === currentAssistantId);
1003
- if (idx >= 0 && prev[idx].role === "assistant" && prev[idx].isStreaming) {
1004
- return prev;
1121
+ if (idx >= 0 && prev[idx].role === "assistant") {
1122
+ const msg = prev[idx];
1123
+ if (msg.content === partial && msg.isStreaming === !isComplete && msg.isComplete === isComplete) {
1124
+ return prev;
1125
+ }
1126
+ const updated = [...prev];
1127
+ updated[idx] = { ...msg, content: partial, isStreaming: !isComplete, isComplete };
1128
+ return updated;
1005
1129
  }
1006
1130
  const last = prev[prev.length - 1];
1007
1131
  if (last && last.role === "assistant" && last.isStreaming) {
1008
1132
  currentAssistantId = last.id;
1009
1133
  pendingStartNewAssistantBubble = false;
1010
- return prev;
1134
+ if (last.content === partial && last.isStreaming === !isComplete && last.isComplete === isComplete) {
1135
+ return prev;
1136
+ }
1137
+ const updated = [...prev];
1138
+ updated[prev.length - 1] = { ...last, content: partial, isStreaming: !isComplete, isComplete };
1139
+ return updated;
1011
1140
  }
1012
1141
  if (pendingStartNewAssistantBubble || !prev.length || (prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming)) {
1013
1142
  const newId = generateId();
@@ -1018,25 +1147,26 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1018
1147
  {
1019
1148
  id: newId,
1020
1149
  role: "assistant",
1021
- content: "",
1150
+ content: partial,
1022
1151
  timestamp: nowTs(),
1023
- isStreaming: true,
1024
- isComplete: false
1152
+ isStreaming: !isComplete,
1153
+ isComplete
1025
1154
  }
1026
1155
  ];
1027
1156
  }
1028
1157
  return prev;
1029
1158
  });
1030
1159
  };
1031
- const updateStreamingMessage = (partial, isComplete) => {
1032
- if (partial && partial.length > 0) {
1033
- hasStreamProgress = true;
1034
- }
1035
- ensureStreamingBubble();
1036
- setMessages((prev) => prev.map((msg) => msg.id === currentAssistantId ? { ...msg, content: partial, isStreaming: !isComplete, isComplete } : msg));
1037
- };
1038
1160
  const finalizeCurrentAssistantBubble = () => {
1039
- setMessages((prev) => prev.map((msg) => msg.id === currentAssistantId ? { ...msg, isStreaming: false, isComplete: true } : msg));
1161
+ setMessages((prev) => {
1162
+ const idx = prev.findIndex((m) => m.id === currentAssistantId);
1163
+ if (idx < 0) return prev;
1164
+ const msg = prev[idx];
1165
+ if (!msg.isStreaming && msg.isComplete) return prev;
1166
+ const updated = [...prev];
1167
+ updated[idx] = { ...msg, isStreaming: false, isComplete: true };
1168
+ return updated;
1169
+ });
1040
1170
  };
1041
1171
  const curThreadId = currentThreadIdRef.current;
1042
1172
  const toServerMessageFromEvent = async (event) => {
@@ -1139,6 +1269,11 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1139
1269
  const currentThreadMetadataMap = threadMetadataMapRef.current;
1140
1270
  const messageMetadata = metadataKey ? currentThreadMetadataMap[metadataKey]?.userContext : void 0;
1141
1271
  const threadMetadata = metadataKey ? currentThreadMetadataMap[metadataKey] : void 0;
1272
+ const mergedMetadata = {
1273
+ ...messageMetadata ?? {},
1274
+ ...params.metadata ?? {}
1275
+ };
1276
+ const finalMetadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
1142
1277
  await runCopilotzStream({
1143
1278
  threadId: params.threadId ?? void 0,
1144
1279
  threadExternalId: params.threadExternalId ?? void 0,
@@ -1152,9 +1287,10 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1152
1287
  }
1153
1288
  },
1154
1289
  attachments: params.attachments,
1155
- metadata: params.metadata ?? messageMetadata,
1290
+ metadata: finalMetadata,
1156
1291
  threadMetadata: params.threadMetadata ?? threadMetadata,
1157
1292
  toolCalls: params.toolCalls,
1293
+ selectedAgent: params.agentName ?? preferredAgentRef.current ?? null,
1158
1294
  onToken: (token, isComplete) => updateStreamingMessage(token, isComplete),
1159
1295
  onMessageEvent: async (event) => {
1160
1296
  const type = event?.type || "";
@@ -1289,7 +1425,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1289
1425
  }
1290
1426
  return currentAssistantId;
1291
1427
  }, [handleStreamMessageEvent, handleStreamAssetEvent]);
1292
- const handleSendMessage = useCallback(async (content, attachments = []) => {
1428
+ const handleSendMessage = useCallback2(async (content, attachments = []) => {
1293
1429
  if (!content.trim() && attachments.length === 0) return;
1294
1430
  if (!userId) return;
1295
1431
  const timestamp = nowTs();
@@ -1349,6 +1485,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1349
1485
  userId,
1350
1486
  // userName can be anything, but let's try to find it in context or just fallback
1351
1487
  userName: userContextSeedRef.current?.profile?.full_name ?? userId,
1488
+ agentName: preferredAgentRef.current,
1352
1489
  // Include pending title for new threads
1353
1490
  threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
1354
1491
  });
@@ -1365,7 +1502,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1365
1502
  } : msg));
1366
1503
  }
1367
1504
  }, [userId, fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage]);
1368
- const bootstrapConversation = useCallback(async (uid) => {
1505
+ const bootstrapConversation = useCallback2(async (uid) => {
1369
1506
  if (!bootstrap?.initialToolCalls && !bootstrap?.initialMessage) return;
1370
1507
  const bootstrapThreadExternalId = generateId();
1371
1508
  setCurrentThreadId(bootstrapThreadExternalId);
@@ -1379,6 +1516,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1379
1516
  content: bootstrap.initialMessage || "",
1380
1517
  toolCalls: bootstrap.initialToolCalls,
1381
1518
  userId: uid,
1519
+ agentName: preferredAgentRef.current,
1382
1520
  threadMetadata: {
1383
1521
  name: defaultThreadName || "Main Thread"
1384
1522
  }
@@ -1400,7 +1538,8 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1400
1538
  ]);
1401
1539
  }
1402
1540
  }, [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName]);
1403
- const reset = useCallback(() => {
1541
+ const reset = useCallback2(() => {
1542
+ messagesRequestRef.current += 1;
1404
1543
  setThreads([]);
1405
1544
  setThreadMetadataMap({});
1406
1545
  setThreadExternalIdMap({});
@@ -1408,17 +1547,19 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1408
1547
  setCurrentThreadExternalId(null);
1409
1548
  setMessages([]);
1410
1549
  setUserContextSeed({});
1550
+ setIsMessagesLoading(false);
1411
1551
  setIsStreaming(false);
1412
1552
  abortControllerRef.current?.abort();
1413
1553
  }, []);
1414
- useEffect(() => {
1554
+ useEffect2(() => {
1415
1555
  if (userId) {
1416
1556
  if (initializationRef.current.userId === userId && initializationRef.current.started) {
1417
1557
  return;
1418
1558
  }
1419
1559
  initializationRef.current = { userId, started: true };
1420
1560
  const init = async () => {
1421
- const preferredThreadId = await fetchAndSetThreadsState(userId, void 0);
1561
+ const urlPreferredThread = isUrlSyncEnabled ? urlState.threadId : void 0;
1562
+ const preferredThreadId = await fetchAndSetThreadsState(userId, urlPreferredThread);
1422
1563
  if (preferredThreadId) {
1423
1564
  await loadThreadMessages(preferredThreadId);
1424
1565
  } else if (bootstrap) {
@@ -1430,8 +1571,13 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1430
1571
  initializationRef.current = { userId: null, started: false };
1431
1572
  reset();
1432
1573
  }
1433
- }, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap]);
1434
- useEffect(() => {
1574
+ }, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap, isUrlSyncEnabled, urlState.threadId]);
1575
+ useEffect2(() => {
1576
+ if (!isUrlSyncEnabled) return;
1577
+ if (!initializationRef.current.started) return;
1578
+ setUrlThreadId(currentThreadExternalId);
1579
+ }, [currentThreadExternalId, isUrlSyncEnabled, setUrlThreadId]);
1580
+ useEffect2(() => {
1435
1581
  if (!currentThreadId) return;
1436
1582
  const metadata = threadMetadataMap[currentThreadId];
1437
1583
  if (!metadata) return;
@@ -1441,6 +1587,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1441
1587
  }, [currentThreadId, threadMetadataMap]);
1442
1588
  return {
1443
1589
  messages,
1590
+ isMessagesLoading,
1444
1591
  threads,
1445
1592
  currentThreadId,
1446
1593
  isStreaming,
@@ -1454,7 +1601,16 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
1454
1601
  stopGeneration: handleStop,
1455
1602
  fetchAndSetThreadsState,
1456
1603
  loadThreadMessages,
1457
- reset
1604
+ reset,
1605
+ // URL state
1606
+ /** Initial prompt from URL (if urlSync enabled) - use for pre-filling input */
1607
+ initialPrompt: isUrlSyncEnabled ? urlState.prompt : null,
1608
+ /** Clear the initial prompt from URL (call after consuming it) */
1609
+ clearInitialPrompt: clearUrlPrompt,
1610
+ /** URL agent ID (if urlSync enabled) - use for agent pre-selection */
1611
+ urlAgentId: isUrlSyncEnabled ? urlState.agentId : null,
1612
+ /** Update agent ID in URL */
1613
+ setUrlAgentId
1458
1614
  };
1459
1615
  }
1460
1616
 
@@ -1476,10 +1632,17 @@ var CopilotzChat = ({
1476
1632
  onAddMemory,
1477
1633
  onUpdateMemory,
1478
1634
  onDeleteMemory,
1479
- className
1635
+ suggestions,
1636
+ agentOptions = [],
1637
+ selectedAgentId = null,
1638
+ onSelectAgent,
1639
+ className,
1640
+ urlSync
1480
1641
  }) => {
1642
+ const selectedAgent = agentOptions.find((agent) => agent.id === selectedAgentId) || null;
1481
1643
  const {
1482
1644
  messages,
1645
+ isMessagesLoading,
1483
1646
  threads,
1484
1647
  currentThreadId,
1485
1648
  isStreaming,
@@ -1490,14 +1653,44 @@ var CopilotzChat = ({
1490
1653
  renameThread,
1491
1654
  archiveThread,
1492
1655
  deleteThread: deleteThread2,
1493
- stopGeneration
1656
+ stopGeneration,
1657
+ initialPrompt,
1658
+ clearInitialPrompt,
1659
+ urlAgentId,
1660
+ setUrlAgentId
1494
1661
  } = useCopilotz({
1495
1662
  userId,
1496
1663
  initialContext,
1497
1664
  bootstrap,
1498
1665
  defaultThreadName: userConfig?.labels?.defaultThreadName,
1499
- onToolOutput
1666
+ onToolOutput,
1667
+ preferredAgentName: selectedAgent?.name ?? null,
1668
+ urlSync
1500
1669
  });
1670
+ const [promptHandled, setPromptHandled] = useState3(false);
1671
+ useEffect3(() => {
1672
+ if (urlAgentId && onSelectAgent && urlAgentId !== selectedAgentId) {
1673
+ const agentExists = agentOptions.some((a) => a.id === urlAgentId);
1674
+ if (agentExists) {
1675
+ onSelectAgent(urlAgentId);
1676
+ }
1677
+ }
1678
+ }, [urlAgentId, selectedAgentId, onSelectAgent, agentOptions]);
1679
+ useEffect3(() => {
1680
+ if (selectedAgentId && urlSync?.enabled) {
1681
+ setUrlAgentId(selectedAgentId);
1682
+ }
1683
+ }, [selectedAgentId, urlSync?.enabled, setUrlAgentId]);
1684
+ useEffect3(() => {
1685
+ if (initialPrompt && !promptHandled && urlSync?.promptBehavior === "auto-send") {
1686
+ const timer = setTimeout(() => {
1687
+ void sendMessage(initialPrompt);
1688
+ clearInitialPrompt();
1689
+ setPromptHandled(true);
1690
+ }, 500);
1691
+ return () => clearTimeout(timer);
1692
+ }
1693
+ }, [initialPrompt, promptHandled, urlSync?.promptBehavior, sendMessage, clearInitialPrompt]);
1501
1694
  const chatCallbacks = useMemo(() => ({
1502
1695
  onSendMessage: (content, attachments) => {
1503
1696
  void sendMessage(content, attachments);
@@ -1560,11 +1753,16 @@ var CopilotzChat = ({
1560
1753
  ChatUI,
1561
1754
  {
1562
1755
  messages,
1756
+ isMessagesLoading,
1563
1757
  threads,
1564
1758
  currentThreadId,
1565
1759
  config: mergedConfig,
1566
1760
  callbacks: chatCallbacks,
1567
1761
  isGenerating: isStreaming,
1762
+ suggestions,
1763
+ agentOptions,
1764
+ selectedAgentId,
1765
+ onSelectAgent,
1568
1766
  user: {
1569
1767
  id: userId,
1570
1768
  name: effectiveUserName,
@@ -1579,7 +1777,12 @@ var CopilotzChat = ({
1579
1777
  onAddMemory,
1580
1778
  onUpdateMemory,
1581
1779
  onDeleteMemory,
1582
- className
1780
+ className,
1781
+ initialInput: initialPrompt && !promptHandled && urlSync?.promptBehavior !== "auto-send" ? initialPrompt : void 0,
1782
+ onInitialInputConsumed: () => {
1783
+ clearInitialPrompt();
1784
+ setPromptHandled(true);
1785
+ }
1583
1786
  }
1584
1787
  ) });
1585
1788
  };
@@ -1594,7 +1797,8 @@ export {
1594
1797
  resolveAssetsInMessages,
1595
1798
  runCopilotzStream,
1596
1799
  updateThread,
1597
- useCopilotz
1800
+ useCopilotz,
1801
+ useUrlState
1598
1802
  };
1599
1803
  /*! Bundled license information:
1600
1804