@copilotz/chat-adapter 0.9.30 → 0.9.32

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
@@ -1605,15 +1605,36 @@ var prepareHydratedMessages = async (rawMessages, options = {}) => {
1605
1605
  };
1606
1606
  };
1607
1607
 
1608
+ // src/streamEvents.ts
1609
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1610
+ var hasValues = (value) => Array.isArray(value) && value.length > 0;
1611
+ var getStreamEventPayload = (event) => {
1612
+ if (!isRecord2(event)) return event;
1613
+ return "payload" in event ? event.payload : event;
1614
+ };
1615
+ var isTerminalEmptyLlmResultEvent = (event) => {
1616
+ if (!isRecord2(event) || event.type !== "LLM_RESULT") return false;
1617
+ const payload = getStreamEventPayload(event);
1618
+ if (!isRecord2(payload)) return false;
1619
+ if (payload.answer !== "") return false;
1620
+ if (payload.finishReason === "tool_calls") return false;
1621
+ if (hasValues(payload.toolCalls)) return false;
1622
+ const metadata = isRecord2(event.metadata) ? event.metadata : {};
1623
+ const routing = isRecord2(metadata.routing) ? metadata.routing : {};
1624
+ if (hasValues(routing.routeTo) || hasValues(routing.askTo)) return false;
1625
+ if (hasValues(metadata.targetQueue)) return false;
1626
+ return true;
1627
+ };
1628
+
1608
1629
  // src/useCopilotzChat.ts
1609
1630
  var nowTs = () => Date.now();
1610
1631
  var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1611
1632
  var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1612
- var getEventPayload = (event) => event?.payload ?? event;
1633
+ var getEventPayload = (event) => getStreamEventPayload(event);
1613
1634
  var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
1614
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1635
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1615
1636
  var normalizeThreadTag = (value) => {
1616
- if (!isRecord2(value)) return null;
1637
+ if (!isRecord3(value)) return null;
1617
1638
  const id = typeof value.id === "string" ? value.id.trim() : "";
1618
1639
  const name = typeof value.name === "string" ? value.name.trim() : "";
1619
1640
  const color = typeof value.color === "string" && value.color.trim() ? value.color.trim() : void 0;
@@ -1621,7 +1642,7 @@ var normalizeThreadTag = (value) => {
1621
1642
  return color ? { id, name, color } : { id, name };
1622
1643
  };
1623
1644
  var getThreadTagsFromMetadata = (metadata) => {
1624
- if (!isRecord2(metadata) || !isRecord2(metadata.public)) return [];
1645
+ if (!isRecord3(metadata) || !isRecord3(metadata.public)) return [];
1625
1646
  const tags = metadata.public.tags;
1626
1647
  if (!Array.isArray(tags)) return [];
1627
1648
  return tags.map(normalizeThreadTag).filter((tag) => !!tag);
@@ -1629,7 +1650,7 @@ var getThreadTagsFromMetadata = (metadata) => {
1629
1650
  var patchMetadataPublicTags = (metadata, tags) => ({
1630
1651
  ...metadata ?? {},
1631
1652
  public: {
1632
- ...isRecord2(metadata?.public) ? metadata.public : {},
1653
+ ...isRecord3(metadata?.public) ? metadata.public : {},
1633
1654
  tags
1634
1655
  }
1635
1656
  });
@@ -1732,6 +1753,9 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
1732
1753
  const userContextSeedRef = useRef2(userContextSeed);
1733
1754
  const messagePageInfoRef = useRef2(messagePageInfo);
1734
1755
  const isLoadingOlderMessagesRef = useRef2(isLoadingOlderMessages);
1756
+ const isStreamingRef = useRef2(isStreaming);
1757
+ const threadActivityStatusRef = useRef2(threadActivityStatus);
1758
+ const isRecoveringStreamRef = useRef2(isRecoveringStream);
1735
1759
  const senderOptionsRef = useRef2({
1736
1760
  agents: agentOptions,
1737
1761
  user: userId ? { id: userId, name: userName, avatarUrl: userAvatar } : null,
@@ -1749,6 +1773,9 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
1749
1773
  userContextSeedRef.current = userContextSeed;
1750
1774
  messagePageInfoRef.current = messagePageInfo;
1751
1775
  isLoadingOlderMessagesRef.current = isLoadingOlderMessages;
1776
+ isStreamingRef.current = isStreaming;
1777
+ threadActivityStatusRef.current = threadActivityStatus;
1778
+ isRecoveringStreamRef.current = isRecoveringStream;
1752
1779
  senderOptionsRef.current = {
1753
1780
  agents: agentOptions,
1754
1781
  user: userId ? { id: userId, name: userName, avatarUrl: userAvatar } : null,
@@ -2293,11 +2320,16 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2293
2320
  async (params) => {
2294
2321
  let currentAssistantId = params.assistantMessageId ?? generateId();
2295
2322
  let currentAssistantSender = params.assistantSender;
2323
+ const streamOwnerThreadId = currentThreadIdRef.current;
2324
+ const streamOwnerThreadExternalId = params.threadExternalId ?? currentThreadExternalIdRef.current;
2325
+ const streamStillOwnsVisibleThread = () => streamOwnerThreadId ? currentThreadIdRef.current === streamOwnerThreadId : !streamOwnerThreadExternalId || currentThreadExternalIdRef.current === streamOwnerThreadExternalId || currentThreadIdRef.current === streamOwnerThreadExternalId;
2296
2326
  params.onBeforeStart?.(currentAssistantId);
2297
2327
  let hasStreamProgress = false;
2298
2328
  const updateStreamingMessage = (partial, opts) => {
2329
+ if (!streamStillOwnsVisibleThread()) return;
2299
2330
  if (partial && partial.length > 0) {
2300
2331
  hasStreamProgress = true;
2332
+ setIsRecoveringStream(false);
2301
2333
  }
2302
2334
  const isReasoning = opts?.isReasoning ?? false;
2303
2335
  const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
@@ -2356,6 +2388,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2356
2388
  });
2357
2389
  };
2358
2390
  const finalizeCurrentAssistantBubble = () => {
2391
+ if (!streamStillOwnsVisibleThread()) return;
2359
2392
  setMessages((prev) => {
2360
2393
  const idx = prev.findIndex((m) => m.id === currentAssistantId);
2361
2394
  if (idx < 0) return prev;
@@ -2368,6 +2401,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2368
2401
  };
2369
2402
  const curThreadId = currentThreadIdRef.current;
2370
2403
  const applyLiveToolResultUpdate = (update) => {
2404
+ if (!streamStillOwnsVisibleThread()) return;
2371
2405
  let matched = false;
2372
2406
  setMessages((prev) => {
2373
2407
  const next = applyToolResultUpdateToMessages(prev, update, {
@@ -2382,6 +2416,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2382
2416
  }
2383
2417
  };
2384
2418
  const finalizeActiveAssistantTurn = (finalAnswer) => {
2419
+ if (!streamStillOwnsVisibleThread()) return;
2385
2420
  setMessages((prev) => {
2386
2421
  const currentIdx = prev.findIndex((message2) => message2.id === currentAssistantId && message2.role === "assistant");
2387
2422
  const fallbackIdx = currentIdx >= 0 ? currentIdx : (() => {
@@ -2429,12 +2464,14 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2429
2464
  return null;
2430
2465
  };
2431
2466
  const abortController = new AbortController();
2432
- abortControllerRef.current?.abort();
2467
+ if (!params.preserveActiveRun) {
2468
+ abortControllerRef.current?.abort();
2469
+ recoveryPollGenerationRef.current += 1;
2470
+ setIsRecoveringStream(false);
2471
+ }
2433
2472
  abortControllerRef.current = abortController;
2434
2473
  stopRequestedRef.current = false;
2435
- recoveryPollGenerationRef.current += 1;
2436
2474
  setThreadActivityStatus("running");
2437
- setIsRecoveringStream(false);
2438
2475
  setIsStreaming(true);
2439
2476
  liveToolUpdatesRef.current = [];
2440
2477
  let streamError = null;
@@ -2488,6 +2525,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2488
2525
  agent: raw?.payload?.agent ?? raw?.agent ?? null
2489
2526
  }),
2490
2527
  onMessageEvent: async (event) => {
2528
+ if (!streamStillOwnsVisibleThread()) return;
2491
2529
  const intercepted = applyEventInterceptor(event);
2492
2530
  if (intercepted?.handled) {
2493
2531
  return;
@@ -2502,6 +2540,9 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2502
2540
  if (type === "LLM_RESULT") {
2503
2541
  const finalAnswer = typeof payload?.answer === "string" ? payload.answer : void 0;
2504
2542
  finalizeActiveAssistantTurn(finalAnswer);
2543
+ if (isTerminalEmptyLlmResultEvent(event)) {
2544
+ finalizeStreamingPlaceholders();
2545
+ }
2505
2546
  return;
2506
2547
  }
2507
2548
  if (type === "MESSAGE" || type === "NEW_MESSAGE") {
@@ -2596,6 +2637,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2596
2637
  handleStreamMessageEvent(event);
2597
2638
  },
2598
2639
  onAssetEvent: async (payload) => {
2640
+ if (!streamStillOwnsVisibleThread()) return;
2599
2641
  const intercepted = applyEventInterceptor({
2600
2642
  type: "ASSET_CREATED",
2601
2643
  payload
@@ -2615,7 +2657,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2615
2657
  }
2616
2658
  const wasStopped = stopRequestedRef.current || abortController.signal.aborted || isAbortError(streamError);
2617
2659
  let recoveryStarted = false;
2618
- if (!wasStopped) {
2660
+ if (!wasStopped && streamStillOwnsVisibleThread()) {
2619
2661
  try {
2620
2662
  const activityThreadId = await resolveActivityThreadId();
2621
2663
  if (activityThreadId) {
@@ -2635,7 +2677,12 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2635
2677
  }
2636
2678
  }
2637
2679
  }
2638
- abortControllerRef.current = null;
2680
+ if (abortControllerRef.current === abortController) {
2681
+ abortControllerRef.current = null;
2682
+ }
2683
+ if (!streamStillOwnsVisibleThread()) {
2684
+ return currentAssistantId;
2685
+ }
2639
2686
  if (recoveryStarted) {
2640
2687
  return currentAssistantId;
2641
2688
  }
@@ -2654,6 +2701,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2654
2701
  const timestamp = nowTs();
2655
2702
  const curThreadId = currentThreadIdRef.current;
2656
2703
  const curThreadExtId = currentThreadExternalIdRef.current;
2704
+ const sendOwnerThreadId = curThreadId;
2705
+ const preserveActiveRun = threadActivityStatusRef.current === "running" || isRecoveringStreamRef.current || isStreamingRef.current;
2657
2706
  const existingThreadId = curThreadId ?? void 0;
2658
2707
  const extMap = threadExternalIdMapRef.current;
2659
2708
  const isPlaceholderThread = existingThreadId ? extMap[existingThreadId] === existingThreadId : false;
@@ -2668,6 +2717,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2668
2717
  setCurrentThreadExternalId(effectiveThreadExternalId ?? null);
2669
2718
  }
2670
2719
  const conversationKey = threadIdForSend ?? effectiveThreadExternalId;
2720
+ const sendOwnerThreadExternalId = effectiveThreadExternalId ?? curThreadExtId;
2721
+ const stillShowingSendOwnerThread = () => sendOwnerThreadId ? currentThreadIdRef.current === sendOwnerThreadId : !sendOwnerThreadExternalId || currentThreadExternalIdRef.current === sendOwnerThreadExternalId || currentThreadIdRef.current === sendOwnerThreadExternalId;
2671
2722
  const currentMetadata = threadMetadataMapRef.current[conversationKey];
2672
2723
  const pendingTitle = currentMetadata?.pendingTitle;
2673
2724
  const userMessage = {
@@ -2727,11 +2778,14 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2727
2778
  agentName: preferredAgentRef.current,
2728
2779
  assistantMessageId: assistantPlaceholder.id,
2729
2780
  assistantSender,
2781
+ preserveActiveRun,
2730
2782
  // Include pending title for new threads
2731
2783
  threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
2732
2784
  });
2733
2785
  await new Promise((r) => setTimeout(r, 1e3));
2734
- await fetchAndSetThreadsState(userId, effectiveThreadExternalId ?? existingThreadId ?? null);
2786
+ if (stillShowingSendOwnerThread()) {
2787
+ await fetchAndSetThreadsState(userId, effectiveThreadExternalId ?? existingThreadId ?? null);
2788
+ }
2735
2789
  } catch (error) {
2736
2790
  if (isAbortError(error)) return;
2737
2791
  console.error("Error sending Copilotz message", error);