@copilotz/chat-adapter 0.9.42 → 0.9.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -57,8 +57,8 @@ type StreamCallbacks = {
57
57
  onToken?: (token: string, isComplete: boolean, raw?: any, options?: {
58
58
  isReasoning?: boolean;
59
59
  }) => void;
60
- onMessageEvent?: (payload: any) => void;
61
- onAssetEvent?: (payload: any) => void;
60
+ onMessageEvent?: (payload: any) => void | Promise<void>;
61
+ onAssetEvent?: (payload: any) => void | Promise<void>;
62
62
  signal?: AbortSignal;
63
63
  };
64
64
  type RunOptions = {
package/dist/index.js CHANGED
@@ -436,7 +436,7 @@ async function runCopilotzStream(options) {
436
436
  lastTokenWasReasoning = false;
437
437
  hadNonReasoningContent = false;
438
438
  };
439
- const processEvent = (eventChunk) => {
439
+ const processEvent = async (eventChunk) => {
440
440
  if (!eventChunk.trim()) return;
441
441
  const lines = eventChunk.split("\n");
442
442
  let eventType = "message";
@@ -496,13 +496,13 @@ async function runCopilotzStream(options) {
496
496
  hadNonReasoningContent = true;
497
497
  lastTokenWasReasoning = false;
498
498
  collectedMessages.push(payload2);
499
- onMessageEvent?.(payload2);
499
+ await onMessageEvent?.(payload2);
500
500
  break;
501
501
  }
502
502
  case "TOOL_CALL": {
503
503
  hadNonReasoningContent = true;
504
504
  lastTokenWasReasoning = false;
505
- onMessageEvent?.(payload2);
505
+ await onMessageEvent?.(payload2);
506
506
  break;
507
507
  }
508
508
  case "TOOL_RESULT":
@@ -512,7 +512,7 @@ async function runCopilotzStream(options) {
512
512
  lastCompletedText = resultAnswer;
513
513
  }
514
514
  resetTokenAggregation();
515
- onMessageEvent?.(payload2);
515
+ await onMessageEvent?.(payload2);
516
516
  break;
517
517
  }
518
518
  case "ASSET_CREATED": {
@@ -522,7 +522,7 @@ async function runCopilotzStream(options) {
522
522
  [assetPayload.assetId || "0"]: assetPayload.dataUrl
523
523
  };
524
524
  }
525
- onAssetEvent?.(assetPayload);
525
+ await onAssetEvent?.(assetPayload);
526
526
  break;
527
527
  }
528
528
  case "ERROR":
@@ -530,9 +530,9 @@ async function runCopilotzStream(options) {
530
530
  default: {
531
531
  const hasEnvelope = payload2 && typeof payload2 === "object" && "type" in payload2;
532
532
  if (hasEnvelope) {
533
- onMessageEvent?.(payload2);
533
+ await onMessageEvent?.(payload2);
534
534
  } else {
535
- onMessageEvent?.({ type: eventType, payload: payload2 });
535
+ await onMessageEvent?.({ type: eventType, payload: payload2 });
536
536
  }
537
537
  break;
538
538
  }
@@ -549,12 +549,12 @@ async function runCopilotzStream(options) {
549
549
  while (eventBoundary >= 0) {
550
550
  const chunk = buffer.slice(0, eventBoundary);
551
551
  buffer = buffer.slice(eventBoundary + SSE_LINE_BREAK.length);
552
- processEvent(chunk);
552
+ await processEvent(chunk);
553
553
  eventBoundary = buffer.indexOf(SSE_LINE_BREAK);
554
554
  }
555
555
  }
556
556
  if (buffer.length > 0) {
557
- processEvent(buffer);
557
+ await processEvent(buffer);
558
558
  }
559
559
  return {
560
560
  text: lastCompletedText || aggregatedText,
@@ -925,27 +925,33 @@ var applyAssistantToolResult = (message, update) => {
925
925
  const index = items.findIndex((item2) => item2.kind === "tool" && (update.id && item2.id === update.id || !update.id && item2.toolName === update.name));
926
926
  if (index === -1) return message;
927
927
  const item = items[index];
928
+ const { error, ...toolCallUpdate } = update;
928
929
  const toolCall = item.details?.toolCall;
929
- const nextToolCall = toolCall ? { ...toolCall, ...update } : {
930
- id: update.id ?? item.id,
931
- name: update.name,
930
+ const nextToolCall = toolCall ? { ...toolCall, ...toolCallUpdate } : {
931
+ id: toolCallUpdate.id ?? item.id,
932
+ name: toolCallUpdate.name,
932
933
  arguments: {},
933
- status: update.status,
934
- ...update.result !== void 0 ? { result: update.result } : {},
935
- ...update.endTime !== void 0 ? { endTime: update.endTime } : {}
934
+ status: toolCallUpdate.status,
935
+ ...toolCallUpdate.result !== void 0 ? { result: toolCallUpdate.result } : {},
936
+ ...toolCallUpdate.endTime !== void 0 ? { endTime: toolCallUpdate.endTime } : {}
936
937
  };
937
938
  const status = toolStatusToActivityStatus(update.status);
939
+ const details = {
940
+ ...item.details ?? {},
941
+ toolCall: nextToolCall,
942
+ ...update.result !== void 0 ? { result: update.result } : {},
943
+ ...error !== void 0 ? { error } : {}
944
+ };
945
+ if (error === void 0 && update.status !== "failed") {
946
+ delete details.error;
947
+ }
938
948
  const next = [...items];
939
949
  next[index] = {
940
950
  ...item,
941
951
  status,
942
952
  toolName: update.name,
943
953
  ...status !== "active" ? { completedAt: update.endTime ?? Date.now() } : {},
944
- details: {
945
- ...item.details ?? {},
946
- toolCall: nextToolCall,
947
- ...update.result !== void 0 ? { result: update.result } : {}
948
- }
954
+ details
949
955
  };
950
956
  return setItems(message, next);
951
957
  };
@@ -1243,8 +1249,18 @@ var expectToolStatus = (status, path) => {
1243
1249
  if (status === "pending") return "pending";
1244
1250
  if (status === "running" || status === "processing") return "running";
1245
1251
  if (status === "failed") return "failed";
1252
+ if (status === "cancelled") return "failed";
1246
1253
  if (status === "completed") return "completed";
1247
- return fail(`${path} must be pending, running, processing, completed, or failed`);
1254
+ return fail(`${path} must be pending, running, processing, completed, failed, or cancelled`);
1255
+ };
1256
+ var formatToolError = (error) => {
1257
+ if (error === void 0) return void 0;
1258
+ if (typeof error === "string") return error;
1259
+ try {
1260
+ return JSON.stringify(error);
1261
+ } catch {
1262
+ return String(error);
1263
+ }
1248
1264
  };
1249
1265
  var expectToolArguments = (value, path) => {
1250
1266
  if (isRecord(value)) return value;
@@ -1271,7 +1287,7 @@ var matchesToolResultUpdate = (target, update) => {
1271
1287
  var findMatchingToolItem = (toolItems, update) => toolItems.find((item) => matchesToolResultUpdate(
1272
1288
  { id: item.id, name: item.toolName },
1273
1289
  update
1274
- ) && (item.status === "active" || item.details?.result === void 0));
1290
+ ) && (item.status === "active" || item.details?.result === void 0 && item.details?.error === void 0));
1275
1291
  var applyToolResultUpdateToMessages = (messages, update, assistantPatch) => {
1276
1292
  const nextMessages = [...messages];
1277
1293
  for (let i = nextMessages.length - 1; i >= 0; i--) {
@@ -1289,6 +1305,7 @@ var applyToolResultUpdateToMessages = (messages, update, assistantPatch) => {
1289
1305
  name: update.name ?? toolItem.toolName ?? toolItem.id,
1290
1306
  status: update.status,
1291
1307
  ...update.result !== void 0 ? { result: update.result } : {},
1308
+ ...update.error !== void 0 ? { error: update.error } : {},
1292
1309
  endTime: update.endTime
1293
1310
  }),
1294
1311
  ...assistantPatch ?? {}
@@ -1314,12 +1331,13 @@ var extractLiveToolResultUpdate = (payload, now = () => Date.now()) => {
1314
1331
  const payloadRecord = expectRecord(payload, "TOOL_RESULT payload");
1315
1332
  const tool = expectRecord(payloadRecord.tool, "TOOL_RESULT payload.tool");
1316
1333
  const result = payloadRecord.projectedOutput !== void 0 ? payloadRecord.projectedOutput : payloadRecord.output;
1317
- if (result === void 0) fail("TOOL_RESULT payload requires output or projectedOutput");
1334
+ const error = formatToolError(payloadRecord.error);
1318
1335
  return {
1319
1336
  id: expectString(payloadRecord.toolCallId, "TOOL_RESULT payload.toolCallId"),
1320
1337
  name: expectToolName(tool, "TOOL_RESULT payload.tool.id"),
1321
1338
  status: expectToolStatus(payloadRecord.status, "TOOL_RESULT payload.status"),
1322
- result,
1339
+ ...result !== void 0 ? { result } : {},
1340
+ ...error !== void 0 ? { error } : {},
1323
1341
  endTime: now()
1324
1342
  };
1325
1343
  };
@@ -1342,6 +1360,7 @@ var extractToolCallsFromServerMessage = (msg) => {
1342
1360
  const name = expectToolName(tool, "toolCall.tool.id");
1343
1361
  const argsRaw = primary.args ?? secondary?.args;
1344
1362
  const result = primary.output !== void 0 ? primary.output : secondary?.output !== void 0 ? secondary.output : primary.projectedOutput !== void 0 ? primary.projectedOutput : secondary?.projectedOutput;
1363
+ const error = formatToolError(primary.error !== void 0 ? primary.error : secondary?.error);
1345
1364
  const rawStatus = primary.status ?? secondary?.status;
1346
1365
  return {
1347
1366
  id,
@@ -1349,6 +1368,7 @@ var extractToolCallsFromServerMessage = (msg) => {
1349
1368
  name,
1350
1369
  arguments: expectToolArguments(argsRaw, "toolCall.args"),
1351
1370
  ...result !== void 0 ? { result } : {},
1371
+ ...error !== void 0 ? { error } : {},
1352
1372
  status: rawStatus === void 0 ? "running" : expectToolStatus(rawStatus, "toolCall.status")
1353
1373
  };
1354
1374
  };
@@ -1374,13 +1394,13 @@ var extractToolResultUpdateFromMessage = (msg, now = () => Date.now()) => {
1374
1394
  const toolCalls = extractToolCallsFromServerMessage(msg);
1375
1395
  if (toolCalls.length === 0) fail("tool message requires metadata.toolCalls");
1376
1396
  const firstToolCall = toolCalls[0];
1377
- if (firstToolCall.result === void 0) fail("tool result message requires tool call output");
1378
1397
  expectStringValue(msg.createdAt, "tool result message.createdAt");
1379
1398
  return {
1380
1399
  id: firstToolCall.id,
1381
1400
  ...firstToolCall.toolExecutionId ? { toolExecutionId: firstToolCall.toolExecutionId } : {},
1382
1401
  name: firstToolCall.name,
1383
- result: firstToolCall.result,
1402
+ ...firstToolCall.result !== void 0 ? { result: firstToolCall.result } : {},
1403
+ ...firstToolCall.error !== void 0 ? { error: firstToolCall.error } : {},
1384
1404
  status: firstToolCall.status,
1385
1405
  endTime: new Date(msg.createdAt).getTime()
1386
1406
  };
@@ -1618,6 +1638,17 @@ var getStreamEventPayload = (event) => {
1618
1638
  if (!isRecord2(event)) return event;
1619
1639
  return "payload" in event ? event.payload : event;
1620
1640
  };
1641
+ var getLlmAttemptId = (event) => {
1642
+ if (!isRecord2(event)) return null;
1643
+ const metadata = isRecord2(event.metadata) ? event.metadata : {};
1644
+ if (typeof metadata.llmAttemptId === "string" && metadata.llmAttemptId.trim().length > 0) {
1645
+ return metadata.llmAttemptId.trim();
1646
+ }
1647
+ if (event.subjectType === "llm_attempt" && typeof event.subjectId === "string" && event.subjectId.trim().length > 0) {
1648
+ return event.subjectId.trim();
1649
+ }
1650
+ return null;
1651
+ };
1621
1652
  var isTerminalEmptyLlmResultEvent = (event) => {
1622
1653
  if (!isRecord2(event) || event.type !== "LLM_RESULT") return false;
1623
1654
  const payload = getStreamEventPayload(event);
@@ -1632,40 +1663,18 @@ var isTerminalEmptyLlmResultEvent = (event) => {
1632
1663
  return true;
1633
1664
  };
1634
1665
 
1635
- // src/useCopilotzChat.ts
1636
- var nowTs = () => Date.now();
1637
- var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1638
- var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1639
- var getEventPayload = (event) => getStreamEventPayload(event);
1640
- var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
1641
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1642
- var normalizeThreadTag = (value) => {
1643
- if (!isRecord3(value)) return null;
1644
- const id = typeof value.id === "string" ? value.id.trim() : "";
1645
- const name = typeof value.name === "string" ? value.name.trim() : "";
1646
- const color = typeof value.color === "string" && value.color.trim() ? value.color.trim() : void 0;
1647
- if (!id || !name) return null;
1648
- return color ? { id, name, color } : { id, name };
1666
+ // src/threadIdentity.ts
1667
+ var identityValues = ({ id, externalId }) => [id, externalId].filter((value) => typeof value === "string" && value.length > 0);
1668
+ var isSameThreadIdentity = (owner, current) => {
1669
+ const ownerValues = identityValues(owner);
1670
+ if (ownerValues.length === 0) return true;
1671
+ const currentValues = new Set(identityValues(current));
1672
+ return ownerValues.some((value) => currentValues.has(value));
1649
1673
  };
1650
- var getThreadTagsFromMetadata = (metadata) => {
1651
- if (!isRecord3(metadata) || !isRecord3(metadata.public)) return [];
1652
- const tags = metadata.public.tags;
1653
- if (!Array.isArray(tags)) return [];
1654
- return tags.map(normalizeThreadTag).filter((tag) => !!tag);
1655
- };
1656
- var patchMetadataPublicTags = (metadata, tags) => ({
1657
- ...metadata ?? {},
1658
- public: {
1659
- ...isRecord3(metadata?.public) ? metadata.public : {},
1660
- tags
1661
- }
1662
- });
1663
- var THREAD_MESSAGES_PAGE_SIZE = 50;
1664
- var createEmptyMessagePageInfo = () => ({
1665
- hasMoreBefore: false,
1666
- oldestMessageId: null,
1667
- newestMessageId: null
1668
- });
1674
+
1675
+ // src/messageReconciliation.ts
1676
+ var CLIENT_MESSAGE_ID_METADATA_KEY = "clientMessageId";
1677
+ var LLM_ATTEMPT_ID_METADATA_KEY = "llmAttemptId";
1669
1678
  var stringifyForCompare = (value) => {
1670
1679
  if (value === void 0) return "";
1671
1680
  try {
@@ -1690,6 +1699,36 @@ var getMessageSignature = (message) => JSON.stringify({
1690
1699
  agentId: message.sender.agentId
1691
1700
  } : null
1692
1701
  });
1702
+ var getMetadataString = (message, key) => {
1703
+ const value = message.metadata?.[key];
1704
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1705
+ };
1706
+ var getCorrelationKeys = (message) => {
1707
+ const clientMessageId = getMetadataString(
1708
+ message,
1709
+ CLIENT_MESSAGE_ID_METADATA_KEY
1710
+ );
1711
+ const llmAttemptId = getMetadataString(
1712
+ message,
1713
+ LLM_ATTEMPT_ID_METADATA_KEY
1714
+ );
1715
+ return [
1716
+ ...clientMessageId ? [`client-message:${message.role}:${clientMessageId}`] : [],
1717
+ ...llmAttemptId ? [`llm-attempt:${message.role}:${llmAttemptId}`] : []
1718
+ ];
1719
+ };
1720
+ var indexUniqueFreshCorrelations = (freshMessages) => {
1721
+ const messagesByCorrelation = /* @__PURE__ */ new Map();
1722
+ for (const message of freshMessages) {
1723
+ for (const key of getCorrelationKeys(message)) {
1724
+ messagesByCorrelation.set(
1725
+ key,
1726
+ messagesByCorrelation.has(key) ? null : message
1727
+ );
1728
+ }
1729
+ }
1730
+ return messagesByCorrelation;
1731
+ };
1693
1732
  var reconcileThreadMessages = (currentMessages, freshMessages) => {
1694
1733
  if (freshMessages.length === 0) {
1695
1734
  return { messages: currentMessages, changed: false };
@@ -1698,12 +1737,15 @@ var reconcileThreadMessages = (currentMessages, freshMessages) => {
1698
1737
  return { messages: freshMessages, changed: true };
1699
1738
  }
1700
1739
  const freshById = new Map(freshMessages.map((message) => [message.id, message]));
1740
+ const freshByCorrelation = indexUniqueFreshCorrelations(freshMessages);
1701
1741
  const seen = /* @__PURE__ */ new Set();
1702
1742
  let changed = false;
1703
1743
  const nextMessages = currentMessages.map((message) => {
1704
- const fresh = freshById.get(message.id);
1744
+ const exactMatch = freshById.get(message.id);
1745
+ const correlatedMatch = exactMatch ? null : getCorrelationKeys(message).map((key) => freshByCorrelation.get(key)).find((candidate) => candidate !== null && candidate !== void 0 && !seen.has(candidate.id));
1746
+ const fresh = exactMatch ?? correlatedMatch;
1705
1747
  if (!fresh) return message;
1706
- seen.add(message.id);
1748
+ seen.add(fresh.id);
1707
1749
  if (getMessageSignature(message) === getMessageSignature(fresh)) {
1708
1750
  return message;
1709
1751
  }
@@ -1721,6 +1763,41 @@ var reconcileThreadMessages = (currentMessages, freshMessages) => {
1721
1763
  }
1722
1764
  return changed ? { messages: nextMessages, changed } : { messages: currentMessages, changed: false };
1723
1765
  };
1766
+
1767
+ // src/useCopilotzChat.ts
1768
+ var nowTs = () => Date.now();
1769
+ var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1770
+ var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1771
+ var getEventPayload = (event) => getStreamEventPayload(event);
1772
+ var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
1773
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1774
+ var normalizeThreadTag = (value) => {
1775
+ if (!isRecord3(value)) return null;
1776
+ const id = typeof value.id === "string" ? value.id.trim() : "";
1777
+ const name = typeof value.name === "string" ? value.name.trim() : "";
1778
+ const color = typeof value.color === "string" && value.color.trim() ? value.color.trim() : void 0;
1779
+ if (!id || !name) return null;
1780
+ return color ? { id, name, color } : { id, name };
1781
+ };
1782
+ var getThreadTagsFromMetadata = (metadata) => {
1783
+ if (!isRecord3(metadata) || !isRecord3(metadata.public)) return [];
1784
+ const tags = metadata.public.tags;
1785
+ if (!Array.isArray(tags)) return [];
1786
+ return tags.map(normalizeThreadTag).filter((tag) => !!tag);
1787
+ };
1788
+ var patchMetadataPublicTags = (metadata, tags) => ({
1789
+ ...metadata ?? {},
1790
+ public: {
1791
+ ...isRecord3(metadata?.public) ? metadata.public : {},
1792
+ tags
1793
+ }
1794
+ });
1795
+ var THREAD_MESSAGES_PAGE_SIZE = 50;
1796
+ var createEmptyMessagePageInfo = () => ({
1797
+ hasMoreBefore: false,
1798
+ oldestMessageId: null,
1799
+ newestMessageId: null
1800
+ });
1724
1801
  var createPendingAssistantActivity = () => ({
1725
1802
  items: [
1726
1803
  {
@@ -2328,7 +2405,10 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2328
2405
  let currentAssistantSender = params.assistantSender;
2329
2406
  const streamOwnerThreadId = currentThreadIdRef.current;
2330
2407
  const streamOwnerThreadExternalId = params.threadExternalId ?? currentThreadExternalIdRef.current;
2331
- const streamStillOwnsVisibleThread = () => streamOwnerThreadId ? currentThreadIdRef.current === streamOwnerThreadId : !streamOwnerThreadExternalId || currentThreadExternalIdRef.current === streamOwnerThreadExternalId || currentThreadIdRef.current === streamOwnerThreadExternalId;
2408
+ const streamStillOwnsVisibleThread = () => isSameThreadIdentity(
2409
+ { id: streamOwnerThreadId, externalId: streamOwnerThreadExternalId },
2410
+ { id: currentThreadIdRef.current, externalId: currentThreadExternalIdRef.current }
2411
+ );
2332
2412
  params.onBeforeStart?.(currentAssistantId);
2333
2413
  let hasStreamProgress = false;
2334
2414
  const updateStreamingMessage = (partial, opts) => {
@@ -2421,7 +2501,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2421
2501
  liveToolUpdatesRef.current.push(update);
2422
2502
  }
2423
2503
  };
2424
- const finalizeActiveAssistantTurn = (finalAnswer) => {
2504
+ const finalizeActiveAssistantTurn = (finalAnswer, llmAttemptId) => {
2425
2505
  if (!streamStillOwnsVisibleThread()) return;
2426
2506
  setMessages((prev) => {
2427
2507
  const currentIdx = prev.findIndex((message2) => message2.id === currentAssistantId && message2.role === "assistant");
@@ -2435,8 +2515,15 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2435
2515
  })();
2436
2516
  if (fallbackIdx < 0) return prev;
2437
2517
  const message = prev[fallbackIdx];
2438
- const nextMessage = finalizeAssistantMessage(message, finalAnswer);
2439
- if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity) {
2518
+ const correlatedMessage = llmAttemptId ? {
2519
+ ...message,
2520
+ metadata: {
2521
+ ...message.metadata ?? {},
2522
+ [LLM_ATTEMPT_ID_METADATA_KEY]: llmAttemptId
2523
+ }
2524
+ } : message;
2525
+ const nextMessage = finalizeAssistantMessage(correlatedMessage, finalAnswer);
2526
+ if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity && message.metadata === nextMessage.metadata) {
2440
2527
  return prev;
2441
2528
  }
2442
2529
  const updated = [...prev];
@@ -2545,7 +2632,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2545
2632
  }
2546
2633
  if (type === "LLM_RESULT") {
2547
2634
  const finalAnswer = typeof payload?.answer === "string" ? payload.answer : void 0;
2548
- finalizeActiveAssistantTurn(finalAnswer);
2635
+ finalizeActiveAssistantTurn(finalAnswer, getLlmAttemptId(event));
2549
2636
  if (isTerminalEmptyLlmResultEvent(event)) {
2550
2637
  finalizeStreamingPlaceholders();
2551
2638
  }
@@ -2724,16 +2811,23 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2724
2811
  }
2725
2812
  const conversationKey = threadIdForSend ?? effectiveThreadExternalId;
2726
2813
  const sendOwnerThreadExternalId = effectiveThreadExternalId ?? curThreadExtId;
2727
- const stillShowingSendOwnerThread = () => sendOwnerThreadId ? currentThreadIdRef.current === sendOwnerThreadId : !sendOwnerThreadExternalId || currentThreadExternalIdRef.current === sendOwnerThreadExternalId || currentThreadIdRef.current === sendOwnerThreadExternalId;
2814
+ const stillShowingSendOwnerThread = () => isSameThreadIdentity(
2815
+ { id: sendOwnerThreadId, externalId: sendOwnerThreadExternalId },
2816
+ { id: currentThreadIdRef.current, externalId: currentThreadExternalIdRef.current }
2817
+ );
2728
2818
  const currentMetadata = threadMetadataMapRef.current[conversationKey];
2729
2819
  const pendingTitle = currentMetadata?.pendingTitle;
2820
+ const userMessageId = generateId();
2730
2821
  const userMessage = {
2731
- id: generateId(),
2822
+ id: userMessageId,
2732
2823
  role: "user",
2733
2824
  content,
2734
2825
  timestamp,
2735
2826
  attachments: attachments.length > 0 ? attachments : void 0,
2736
2827
  isComplete: true,
2828
+ metadata: {
2829
+ [CLIENT_MESSAGE_ID_METADATA_KEY]: userMessageId
2830
+ },
2737
2831
  sender: resolveUserSender({
2738
2832
  id: userId,
2739
2833
  name: getCurrentUserDisplayName(userName, userId)
@@ -2782,6 +2876,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2782
2876
  userId,
2783
2877
  userName: getCurrentUserDisplayName(userName, userId),
2784
2878
  agentName: preferredAgentRef.current,
2879
+ metadata: userMessage.metadata,
2785
2880
  assistantMessageId: assistantPlaceholder.id,
2786
2881
  assistantSender,
2787
2882
  preserveActiveRun,