@copilotz/chat-adapter 0.7.0 → 0.7.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
@@ -672,64 +672,6 @@ async function deleteThread(threadId, getRequestHeaders) {
672
672
  return true;
673
673
  }
674
674
 
675
- // src/assetsService.ts
676
- var rawBaseValue2 = import.meta.env?.VITE_API_URL;
677
- var rawBase2 = typeof rawBaseValue2 === "string" && rawBaseValue2.length > 0 ? rawBaseValue2 : "/api";
678
- var normalizedBase2 = rawBase2.replace(/\/$/, "");
679
- var API_BASE2 = normalizedBase2.startsWith("http") || normalizedBase2.startsWith("/") ? normalizedBase2 : `/${normalizedBase2}`;
680
- var apiUrl2 = (path) => `${API_BASE2}${path}`;
681
- var extractAssetId = (refOrId) => refOrId.startsWith("asset://") ? refOrId.slice("asset://".length) : refOrId;
682
- async function getAssetDataUrl(refOrId) {
683
- const id = extractAssetId(refOrId);
684
- const res = await fetch(apiUrl2(`/v1/assets/${encodeURIComponent(id)}?format=dataUrl`), {
685
- method: "GET",
686
- headers: { Accept: "application/json" }
687
- });
688
- if (!res.ok) {
689
- const text = await res.text().catch(() => res.statusText);
690
- throw new Error(text || `Failed to fetch asset ${refOrId}`);
691
- }
692
- const body = await res.json();
693
- const data = body?.data ?? body;
694
- if (!data?.dataUrl) {
695
- throw new Error(data?.error || `Asset ${refOrId} has no dataUrl`);
696
- }
697
- return { dataUrl: data.dataUrl, mime: data.mime, assetId: data.assetId };
698
- }
699
- async function resolveAssetsInMessages(messages) {
700
- const inFlightByRef = /* @__PURE__ */ new Map();
701
- const resolveAssetRef = (assetRef) => {
702
- if (!inFlightByRef.has(assetRef)) {
703
- inFlightByRef.set(assetRef, getAssetDataUrl(assetRef));
704
- }
705
- return inFlightByRef.get(assetRef);
706
- };
707
- return Promise.all(messages.map(async (msg) => {
708
- const meta = msg.metadata ?? void 0;
709
- const attachments = Array.isArray(meta?.attachments) ? meta.attachments : void 0;
710
- if (!attachments || attachments.length === 0) {
711
- return msg;
712
- }
713
- const newAttachments = await Promise.all(attachments.map(async (att) => {
714
- const assetRef = typeof att?.assetRef === "string" ? att.assetRef : void 0;
715
- if (!assetRef) return att;
716
- try {
717
- const { dataUrl, mime } = await resolveAssetRef(assetRef);
718
- const kind = typeof att.kind === "string" ? att.kind : "image";
719
- return {
720
- kind,
721
- dataUrl,
722
- mimeType: typeof att.mimeType === "string" ? att.mimeType : mime ?? void 0
723
- };
724
- } catch {
725
- return att;
726
- }
727
- }));
728
- const newMeta = { ...meta ?? {}, attachments: newAttachments };
729
- return { ...msg, metadata: newMeta };
730
- }));
731
- }
732
-
733
675
  // src/useUrlState.ts
734
676
  import { useState, useEffect, useCallback, useRef } from "react";
735
677
  var DEFAULT_PARAMS = {
@@ -835,194 +777,397 @@ function useUrlState(config = {}) {
835
777
  }
836
778
 
837
779
  // src/activity.ts
838
- var isToolCallActive = (toolCall) => toolCall.status === "pending" || toolCall.status === "running";
780
+ var thinkingId = "thinking";
781
+ var answeringId = "answering";
782
+ var getItems = (message) => Array.isArray(message.activity?.items) ? message.activity.items : [];
783
+ var setItems = (message, items) => ({
784
+ ...message,
785
+ activity: items.length > 0 ? { items } : void 0
786
+ });
787
+ var toolStatusToActivityStatus = (status) => {
788
+ if (status === "failed") return "failed";
789
+ if (status === "completed") return "complete";
790
+ return "active";
791
+ };
792
+ var upsertItem = (message, item) => {
793
+ const items = getItems(message);
794
+ const index = items.findIndex((current) => current.id === item.id);
795
+ if (index === -1) return setItems(message, [...items, item]);
796
+ const next = [...items];
797
+ next[index] = {
798
+ ...next[index],
799
+ ...item,
800
+ details: {
801
+ ...next[index].details ?? {},
802
+ ...item.details ?? {}
803
+ }
804
+ };
805
+ return setItems(message, next);
806
+ };
807
+ var completeItems = (message, shouldComplete) => setItems(message, getItems(message).map((item) => item.status === "active" && shouldComplete(item) ? { ...item, status: "complete", completedAt: Date.now() } : item));
839
808
  var hasVisibleAssistantOutput = (message) => {
840
809
  if (message.role !== "assistant") return false;
841
810
  if (typeof message.content === "string" && message.content.trim().length > 0) return true;
842
811
  if (Array.isArray(message.attachments) && message.attachments.length > 0) return true;
843
- if (Array.isArray(message._activityToolCalls) && message._activityToolCalls.length > 0) return true;
844
- return false;
845
- };
846
- var buildAssistantActivity = (message) => {
847
- const toolCalls = Array.isArray(message._activityToolCalls) ? message._activityToolCalls : [];
848
- const hasReasoning = typeof message._activityReasoning === "string" && message._activityReasoning.length > 0;
849
- const hasToolCalls = toolCalls.length > 0;
850
- const runningTools = toolCalls.filter(isToolCallActive);
851
- const hasRunningTools = runningTools.length > 0;
852
- const isStreaming = message.isStreaming === true;
853
- const isReasoningStreaming = message._activityReasoningStreaming === true;
854
- const hasContent = typeof message.content === "string" && message.content.trim().length > 0;
855
- if (!hasReasoning && !hasToolCalls && !isStreaming && !isReasoningStreaming) {
856
- return void 0;
857
- }
858
- const isActive = isStreaming || isReasoningStreaming || hasRunningTools;
859
- const summary = hasRunningTools ? {
860
- kind: "using_tools",
861
- ...runningTools.length === 1 ? { toolName: runningTools[0].name } : {},
862
- ...runningTools.length > 1 ? { toolCount: runningTools.length } : {}
863
- } : isStreaming && hasToolCalls && !hasContent ? {
864
- kind: "using_tools",
865
- ...toolCalls.length === 1 ? { toolName: toolCalls[0].name } : {},
866
- ...toolCalls.length > 1 ? { toolCount: toolCalls.length } : {}
867
- } : isReasoningStreaming || !hasContent && hasReasoning ? { kind: "thinking" } : isStreaming && hasContent ? { kind: "preparing_answer" } : isStreaming ? { kind: "working" } : hasToolCalls ? {
868
- kind: "using_tools",
869
- ...toolCalls.length === 1 ? { toolName: toolCalls[0].name } : {},
870
- ...toolCalls.length > 1 ? { toolCount: toolCalls.length } : {}
871
- } : { kind: "thinking" };
872
- return {
873
- isActive,
874
- ...isActive ? {} : { isComplete: true },
875
- summary,
876
- ...hasReasoning ? { reasoning: message._activityReasoning } : {},
877
- ...hasToolCalls ? { toolCalls } : {}
878
- };
879
- };
880
- var syncAssistantActivity = (message) => {
881
- if (message.role !== "assistant") {
882
- const { _activityReasoning, _activityReasoningStreaming, _activityToolCalls, ...rest } = message;
883
- return rest;
884
- }
885
- return {
886
- ...message,
887
- activity: buildAssistantActivity(message)
888
- };
812
+ return getItems(message).length > 0;
889
813
  };
890
814
  var toPublicChatMessage = (message) => {
891
- const { _activityReasoning, _activityReasoningStreaming, _activityToolCalls, ...rest } = syncAssistantActivity(message);
815
+ if (message.role === "assistant") return message;
816
+ const { activity, ...rest } = message;
892
817
  return rest;
893
818
  };
894
819
  var updateAssistantMessageToken = (message, params) => {
895
820
  if (message.role !== "assistant") return message;
896
- const next = params.isReasoning ? {
897
- ...message,
898
- ...params.agentIdentity,
899
- _activityReasoning: params.partial,
900
- _activityReasoningStreaming: true,
901
- isStreaming: true,
902
- isComplete: false
903
- } : {
821
+ if (params.isReasoning) {
822
+ return upsertItem({
823
+ ...message,
824
+ isStreaming: true,
825
+ isComplete: false
826
+ }, {
827
+ id: thinkingId,
828
+ kind: "thinking",
829
+ status: "active",
830
+ startedAt: getItems(message).find((item) => item.id === thinkingId)?.startedAt ?? Date.now(),
831
+ details: { reasoning: params.partial }
832
+ });
833
+ }
834
+ return upsertItem(completeItems({
904
835
  ...message,
905
- ...params.agentIdentity,
906
836
  content: params.partial,
907
- _activityReasoningStreaming: false,
908
837
  isStreaming: true,
909
838
  isComplete: false
910
- };
911
- return syncAssistantActivity(next);
839
+ }, (item) => item.kind === "thinking" || item.kind === "tool"), {
840
+ id: answeringId,
841
+ kind: "answering",
842
+ status: "active",
843
+ startedAt: getItems(message).find((item) => item.id === answeringId)?.startedAt ?? Date.now()
844
+ });
912
845
  };
913
846
  var appendAssistantToolCall = (message, toolCall) => {
914
847
  if (message.role !== "assistant") return message;
915
- return syncAssistantActivity({
848
+ const status = toolStatusToActivityStatus(toolCall.status);
849
+ return upsertItem({
916
850
  ...message,
917
- _activityToolCalls: [
918
- ...Array.isArray(message._activityToolCalls) ? message._activityToolCalls : [],
919
- toolCall
920
- ],
921
851
  isStreaming: true,
922
852
  isComplete: false
853
+ }, {
854
+ id: toolCall.id,
855
+ kind: "tool",
856
+ status,
857
+ toolName: toolCall.name,
858
+ startedAt: toolCall.startTime ?? Date.now(),
859
+ ...status !== "active" ? { completedAt: toolCall.endTime ?? Date.now() } : {},
860
+ details: {
861
+ toolCall,
862
+ ...toolCall.result !== void 0 ? { result: toolCall.result } : {}
863
+ }
923
864
  });
924
865
  };
925
866
  var applyAssistantToolResult = (message, update) => {
926
867
  if (message.role !== "assistant") return message;
927
- const toolCalls = Array.isArray(message._activityToolCalls) ? message._activityToolCalls : [];
928
- const nextToolCalls = toolCalls.map((toolCall) => {
929
- const matchesById = update.id && toolCall.id === update.id;
930
- const matchesByName = !update.id && toolCall.name === update.name;
931
- if (!matchesById && !matchesByName) return toolCall;
932
- return {
933
- ...toolCall,
934
- ...update
935
- };
936
- });
937
- return syncAssistantActivity({
938
- ...message,
939
- _activityToolCalls: nextToolCalls
940
- });
868
+ const items = getItems(message);
869
+ const index = items.findIndex((item2) => item2.kind === "tool" && (update.id && item2.id === update.id || !update.id && item2.toolName === update.name));
870
+ if (index === -1) return message;
871
+ const item = items[index];
872
+ const toolCall = item.details?.toolCall;
873
+ const nextToolCall = toolCall ? { ...toolCall, ...update } : {
874
+ id: update.id ?? item.id,
875
+ name: update.name,
876
+ arguments: {},
877
+ status: update.status,
878
+ ...update.result !== void 0 ? { result: update.result } : {},
879
+ ...update.endTime !== void 0 ? { endTime: update.endTime } : {}
880
+ };
881
+ const status = toolStatusToActivityStatus(update.status);
882
+ const next = [...items];
883
+ next[index] = {
884
+ ...item,
885
+ status,
886
+ toolName: update.name,
887
+ ...status !== "active" ? { completedAt: update.endTime ?? Date.now() } : {},
888
+ details: {
889
+ ...item.details ?? {},
890
+ toolCall: nextToolCall,
891
+ ...update.result !== void 0 ? { result: update.result } : {}
892
+ }
893
+ };
894
+ return setItems(message, next);
941
895
  };
942
896
  var finalizeAssistantMessage = (message, finalAnswer) => {
943
897
  if (message.role !== "assistant") return message;
944
- return syncAssistantActivity({
898
+ const completed = completeItems({
945
899
  ...message,
946
900
  ...typeof finalAnswer === "string" && finalAnswer.length > 0 ? { content: finalAnswer } : {},
947
901
  isStreaming: false,
948
- isComplete: true,
949
- _activityReasoningStreaming: false
950
- });
902
+ isComplete: true
903
+ }, (item) => item.kind === "thinking" || item.kind === "answering");
904
+ return setItems(completed, getItems(completed).filter((item) => item.kind !== "answering"));
951
905
  };
952
906
  var closeAssistantMessage = (message) => {
953
907
  if (message.role !== "assistant") return message;
954
- return syncAssistantActivity({
908
+ return completeItems({
955
909
  ...message,
956
910
  isStreaming: false,
957
- isComplete: true,
958
- _activityReasoningStreaming: false
959
- });
911
+ isComplete: true
912
+ }, () => true);
960
913
  };
961
914
 
962
- // src/useCopilotzChat.ts
963
- var nowTs = () => Date.now();
964
- var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
965
- var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
966
- var getEventPayload = (event) => event?.payload ?? event;
967
- var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
968
- var isInternalMessageMetadata = (metadata) => metadata?.visibility === "internal";
969
- var normalizeAgentIdentity = (agent) => {
970
- const senderAgentId = typeof agent?.id === "string" && agent.id.length > 0 ? agent.id : void 0;
971
- const senderName = typeof agent?.name === "string" && agent.name.length > 0 ? agent.name : senderAgentId;
972
- return {
973
- ...senderAgentId ? { senderAgentId } : {},
974
- ...senderName ? { senderName } : {}
975
- };
915
+ // src/contract.ts
916
+ var ContractViolation = class extends Error {
917
+ constructor(message) {
918
+ super(message);
919
+ this.name = "ContractViolation";
920
+ }
976
921
  };
977
- var messageAgentKey = (message) => {
978
- if (message.role !== "assistant") return null;
979
- return message.senderAgentId ?? message.senderName ?? null;
922
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
923
+ var expectRecord = (value, path) => {
924
+ if (!isRecord(value)) throw new ContractViolation(`${path} must be an object`);
925
+ return value;
926
+ };
927
+ var expectString = (value, path) => {
928
+ if (typeof value !== "string" || value.trim().length === 0) throw new ContractViolation(`${path} must be a non-empty string`);
929
+ return value;
930
+ };
931
+ var expectOptionalString = (value, path) => {
932
+ if (value === void 0 || value === null) return void 0;
933
+ return expectString(value, path);
980
934
  };
981
- var resolveLiveAgentIdentity = (event) => {
982
- const payload = getEventPayload(event);
983
- const agent = payload?.agent && typeof payload.agent === "object" ? payload.agent : event?.agent && typeof event.agent === "object" ? event.agent : null;
935
+ var expectStringValue = (value, path) => {
936
+ if (typeof value !== "string") throw new ContractViolation(`${path} must be a string`);
937
+ return value;
938
+ };
939
+
940
+ // src/senders.ts
941
+ var clean = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
942
+ var expectSenderType = (value, path) => {
943
+ if (value === "user" || value === "agent" || value === "tool" || value === "system") return value;
944
+ throw new ContractViolation(`${path} must be user, agent, tool, or system`);
945
+ };
946
+ var defined = (value) => Object.fromEntries(
947
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
948
+ );
949
+ var findAgent = (agents, ...candidates) => {
950
+ const values = candidates.filter(Boolean).map((value) => value.toLowerCase());
951
+ if (!agents || values.length === 0) return void 0;
952
+ return agents.find(
953
+ (agent) => values.includes(agent.id.toLowerCase()) || values.includes(agent.name.toLowerCase())
954
+ );
955
+ };
956
+ var fromAgent = (agent, overrides = {}) => defined({
957
+ type: overrides.type ?? "agent",
958
+ id: agent.id,
959
+ name: agent.name,
960
+ agentId: agent.id,
961
+ avatarUrl: agent.avatarUrl,
962
+ color: agent.color,
963
+ ...overrides
964
+ });
965
+ var resolveUserSender = (user) => defined({
966
+ type: "user",
967
+ id: user.id,
968
+ externalId: user.id,
969
+ name: clean(user.name) ?? user.id,
970
+ avatarUrl: clean(user.avatarUrl)
971
+ });
972
+ var resolveAssistantFallbackSender = (options = {}) => ({
973
+ type: "agent",
974
+ id: "assistant",
975
+ name: clean(options.assistantName) ?? "Assistant",
976
+ agentId: "assistant"
977
+ });
978
+ var resolveAgentSender = (identity, options = {}, overrides = {}) => {
979
+ const id = expectString(identity.id, "agent.id");
980
+ const name = expectString(identity.name, "agent.name");
981
+ const agent = findAgent(options.agents, id, name);
984
982
  if (agent) {
985
- return normalizeAgentIdentity(agent);
983
+ return fromAgent(agent, defined({
984
+ ...overrides,
985
+ externalId: id && id !== agent.id ? id : void 0
986
+ }));
986
987
  }
987
- const sender = payload?.sender && typeof payload.sender === "object" ? payload.sender : event?.sender && typeof event.sender === "object" ? event.sender : null;
988
- return normalizeAgentIdentity({
989
- id: typeof payload?.senderId === "string" ? payload.senderId : typeof sender?.id === "string" ? sender.id : null,
990
- name: typeof payload?.senderName === "string" ? payload.senderName : typeof sender?.name === "string" ? sender.name : null
988
+ return defined({
989
+ type: overrides.type ?? "agent",
990
+ id,
991
+ name,
992
+ agentId: id,
993
+ ...overrides
991
994
  });
992
995
  };
993
- var canAttachToStreamingAssistant = (message, incomingAgentKey) => {
994
- if (!message || message.role !== "assistant" || !message.isStreaming) {
995
- return false;
996
+ var resolveHydratedMessageSender = (message, options = {}) => {
997
+ const metadata = message.metadata ? expectRecord(message.metadata, "message.metadata") : {};
998
+ const type = expectSenderType(message.senderType, "message.senderType");
999
+ const storedId = expectOptionalString(message.senderId, "message.senderId");
1000
+ const participantId = expectOptionalString(metadata.senderParticipantId, "message.metadata.senderParticipantId") ?? expectOptionalString(message.senderUserId, "message.senderUserId");
1001
+ const externalId = expectString(metadata.senderExternalId, "message.metadata.senderExternalId");
1002
+ const displayName = expectString(metadata.senderDisplayName, "message.metadata.senderDisplayName");
1003
+ if (type === "agent" || type === "tool") {
1004
+ const agent = findAgent(options.agents, externalId, displayName, storedId);
1005
+ if (agent) {
1006
+ return fromAgent(agent, defined({
1007
+ type,
1008
+ participantId: participantId ?? storedId,
1009
+ externalId: externalId && externalId !== agent.id ? externalId : void 0
1010
+ }));
1011
+ }
1012
+ return defined({
1013
+ type,
1014
+ id: externalId,
1015
+ name: displayName,
1016
+ agentId: externalId,
1017
+ participantId: participantId ?? storedId,
1018
+ externalId
1019
+ });
996
1020
  }
997
- const currentAgentKey = messageAgentKey(message);
998
- return !incomingAgentKey || !currentAgentKey || currentAgentKey === incomingAgentKey;
1021
+ if (type === "user") {
1022
+ return defined({
1023
+ type: "user",
1024
+ id: externalId,
1025
+ externalId,
1026
+ name: displayName,
1027
+ avatarUrl: clean(options.user?.avatarUrl),
1028
+ participantId: participantId ?? storedId
1029
+ });
1030
+ }
1031
+ return defined({
1032
+ type: "system",
1033
+ id: externalId,
1034
+ name: displayName,
1035
+ participantId: participantId ?? storedId,
1036
+ externalId
1037
+ });
999
1038
  };
1000
- var THREAD_MESSAGES_PAGE_SIZE = 50;
1001
- var createEmptyMessagePageInfo = () => ({
1002
- hasMoreBefore: false,
1003
- oldestMessageId: null,
1004
- newestMessageId: null
1005
- });
1006
- var normalizeToolStatus = (status) => {
1039
+ var resolveLiveEventSender = (event, options = {}) => {
1040
+ const raw = expectRecord(event, "stream event");
1041
+ const payload = raw.payload === void 0 ? raw : expectRecord(raw.payload, "stream event.payload");
1042
+ const agent = payload.agent ?? raw.agent;
1043
+ if (isRecord(agent)) {
1044
+ return resolveAgentSender({
1045
+ id: expectString(agent.id, "stream event.payload.agent.id"),
1046
+ name: expectString(agent.name, "stream event.payload.agent.name")
1047
+ }, options);
1048
+ }
1049
+ const sender = payload.sender ?? raw.sender;
1050
+ if (!isRecord(sender)) {
1051
+ throw new ContractViolation("stream event sender contract requires payload.agent or payload.sender");
1052
+ }
1053
+ const type = expectSenderType(sender.type ?? payload.senderType, "stream event.payload.sender.type");
1054
+ if (type !== "user") {
1055
+ return resolveAgentSender({
1056
+ id: expectString(sender.id ?? sender.externalId, "stream event.payload.sender.id"),
1057
+ name: expectString(sender.name, "stream event.payload.sender.name")
1058
+ }, options, { type });
1059
+ }
1060
+ if (!options.user) {
1061
+ throw new ContractViolation("user stream sender requires current user context");
1062
+ }
1063
+ return resolveUserSender(options.user);
1064
+ };
1065
+
1066
+ // src/assetsService.ts
1067
+ var ContractViolation2 = class extends Error {
1068
+ name = "ContractViolation";
1069
+ };
1070
+ var expectRecord2 = (value, path) => {
1071
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
1072
+ throw new ContractViolation2(`${path} must be an object`);
1073
+ };
1074
+ var expectString2 = (value, path) => {
1075
+ if (typeof value === "string" && value.trim().length > 0) return value;
1076
+ throw new ContractViolation2(`${path} must be a non-empty string`);
1077
+ };
1078
+ var rawBaseValue2 = import.meta.env?.VITE_API_URL;
1079
+ var rawBase2 = typeof rawBaseValue2 === "string" && rawBaseValue2.length > 0 ? rawBaseValue2 : "/api";
1080
+ var normalizedBase2 = rawBase2.replace(/\/$/, "");
1081
+ var API_BASE2 = normalizedBase2.startsWith("http") || normalizedBase2.startsWith("/") ? normalizedBase2 : `/${normalizedBase2}`;
1082
+ var apiUrl2 = (path) => `${API_BASE2}${path}`;
1083
+ var extractAssetId = (refOrId) => refOrId.startsWith("asset://") ? refOrId.slice("asset://".length) : refOrId;
1084
+ async function getAssetDataUrl(refOrId) {
1085
+ const id = extractAssetId(refOrId);
1086
+ const res = await fetch(apiUrl2(`/v1/assets/${encodeURIComponent(id)}?format=dataUrl`), {
1087
+ method: "GET",
1088
+ headers: { Accept: "application/json" }
1089
+ });
1090
+ if (!res.ok) {
1091
+ const text = await res.text().catch(() => res.statusText);
1092
+ throw new Error(text || `Failed to fetch asset ${refOrId}`);
1093
+ }
1094
+ const body = await res.json();
1095
+ const envelope = expectRecord2(body, "asset response");
1096
+ const data = expectRecord2(envelope.data, "asset response.data");
1097
+ if (typeof data.error === "string" && data.error.length > 0) {
1098
+ throw new Error(data.error);
1099
+ }
1100
+ if (typeof data.dataUrl !== "string" || data.dataUrl.length === 0) {
1101
+ throw new ContractViolation2(`asset response.data.dataUrl is required for ${refOrId}`);
1102
+ }
1103
+ return {
1104
+ dataUrl: data.dataUrl,
1105
+ mime: typeof data.mime === "string" ? data.mime : void 0,
1106
+ assetId: expectString2(data.assetId, "asset response.data.assetId")
1107
+ };
1108
+ }
1109
+ async function resolveAssetsInMessages(messages) {
1110
+ const inFlightByRef = /* @__PURE__ */ new Map();
1111
+ const resolveAssetRef = (assetRef) => {
1112
+ if (!inFlightByRef.has(assetRef)) {
1113
+ inFlightByRef.set(assetRef, getAssetDataUrl(assetRef));
1114
+ }
1115
+ return inFlightByRef.get(assetRef);
1116
+ };
1117
+ return Promise.all(messages.map(async (msg) => {
1118
+ const meta = msg.metadata === null || msg.metadata === void 0 ? void 0 : expectRecord2(msg.metadata, "message.metadata");
1119
+ const attachments = meta?.attachments === void 0 ? void 0 : Array.isArray(meta.attachments) ? meta.attachments.map(
1120
+ (att, index) => expectRecord2(att, `message.metadata.attachments[${index}]`)
1121
+ ) : (() => {
1122
+ throw new ContractViolation2("message.metadata.attachments must be an array");
1123
+ })();
1124
+ if (!attachments || attachments.length === 0) {
1125
+ return msg;
1126
+ }
1127
+ const newAttachments = await Promise.all(attachments.map(async (att, index) => {
1128
+ const assetRef = typeof att.assetRef === "string" ? att.assetRef : void 0;
1129
+ if (!assetRef) return att;
1130
+ const { dataUrl, mime } = await resolveAssetRef(assetRef);
1131
+ const kind = expectString2(att.kind, `message.metadata.attachments[${index}].kind`);
1132
+ const mimeType = typeof att.mimeType === "string" ? att.mimeType : expectString2(mime, `asset ${assetRef}.mime`);
1133
+ return {
1134
+ ...att,
1135
+ kind,
1136
+ dataUrl,
1137
+ mimeType
1138
+ };
1139
+ }));
1140
+ const newMeta = { ...meta, attachments: newAttachments };
1141
+ return { ...msg, metadata: newMeta };
1142
+ }));
1143
+ }
1144
+
1145
+ // src/toolActivity.ts
1146
+ var fail = (message) => {
1147
+ throw new ContractViolation(message);
1148
+ };
1149
+ var expectToolStatus = (status, path) => {
1007
1150
  if (status === "pending") return "pending";
1008
1151
  if (status === "running" || status === "processing") return "running";
1009
1152
  if (status === "failed") return "failed";
1010
- return "completed";
1153
+ if (status === "completed") return "completed";
1154
+ return fail(`${path} must be pending, running, processing, completed, or failed`);
1011
1155
  };
1012
- var parseToolArguments = (value) => {
1013
- if (value && typeof value === "object" && !Array.isArray(value)) {
1014
- return value;
1015
- }
1156
+ var expectToolArguments = (value, path) => {
1157
+ if (isRecord(value)) return value;
1016
1158
  if (typeof value === "string") {
1017
1159
  try {
1018
1160
  const parsed = JSON.parse(value);
1019
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1020
- return parsed;
1021
- }
1161
+ return expectRecord(parsed, path);
1022
1162
  } catch {
1163
+ return fail(`${path} must be an object or JSON object string`);
1023
1164
  }
1024
1165
  }
1025
- return {};
1166
+ return fail(`${path} must be an object or JSON object string`);
1167
+ };
1168
+ var expectToolName = (tool, path) => {
1169
+ const name = typeof tool.name === "string" && tool.name.trim().length > 0 ? tool.name : tool.id;
1170
+ return expectString(name, path);
1026
1171
  };
1027
1172
  var matchesToolResultUpdate = (target, update) => {
1028
1173
  if (update.id && target.id) {
@@ -1030,92 +1175,84 @@ var matchesToolResultUpdate = (target, update) => {
1030
1175
  }
1031
1176
  return Boolean(update.name && target.name && update.name === target.name);
1032
1177
  };
1033
- var findMatchingToolCallIndex = (toolCalls, update) => toolCalls.findIndex((toolCall) => matchesToolResultUpdate(
1034
- { id: toolCall.id, name: toolCall.name },
1178
+ var findMatchingToolItem = (toolItems, update) => toolItems.find((item) => matchesToolResultUpdate(
1179
+ { id: item.id, name: item.toolName },
1035
1180
  update
1036
- ) && (toolCall.status === "pending" || toolCall.status === "running" || typeof toolCall.result === "undefined"));
1181
+ ) && (item.status === "active" || item.details?.result === void 0));
1037
1182
  var applyToolResultUpdateToMessages = (messages, update, assistantPatch) => {
1038
1183
  const nextMessages = [...messages];
1039
1184
  for (let i = nextMessages.length - 1; i >= 0; i--) {
1040
1185
  const message = nextMessages[i];
1041
- if (message.role !== "assistant" || !Array.isArray(message._activityToolCalls) || message._activityToolCalls.length === 0) {
1186
+ const toolItems = message.activity?.items.filter((item) => item.kind === "tool") ?? [];
1187
+ if (message.role !== "assistant" || toolItems.length === 0) {
1042
1188
  continue;
1043
1189
  }
1044
- const toolCallIndex = findMatchingToolCallIndex(message._activityToolCalls, update);
1045
- if (toolCallIndex === -1) continue;
1046
- nextMessages[i] = syncAssistantActivity({
1190
+ const toolItem = findMatchingToolItem(toolItems, update);
1191
+ if (!toolItem) continue;
1192
+ nextMessages[i] = {
1047
1193
  ...applyAssistantToolResult(message, {
1048
1194
  ...update.id ? { id: update.id } : {},
1049
- name: update.name ?? message._activityToolCalls[toolCallIndex].name,
1195
+ name: update.name ?? toolItem.toolName ?? toolItem.id,
1050
1196
  status: update.status,
1051
1197
  ...update.result !== void 0 ? { result: update.result } : {},
1052
1198
  endTime: update.endTime
1053
1199
  }),
1054
1200
  ...assistantPatch ?? {}
1055
- });
1201
+ };
1056
1202
  return { messages: nextMessages, matched: true };
1057
1203
  }
1058
1204
  return { messages, matched: false };
1059
1205
  };
1060
1206
  var extractLiveToolCall = (payload) => {
1061
- const toolCall = payload?.toolCall;
1062
- if (!toolCall) return null;
1063
- const tool = toolCall.tool;
1064
- const name = typeof tool?.name === "string" ? tool.name : typeof tool?.id === "string" ? tool.id : "tool";
1065
- const result = toolCall.output !== void 0 ? toolCall.output : void 0;
1207
+ const payloadRecord = expectRecord(payload, "TOOL_CALL payload");
1208
+ const toolCall = expectRecord(payloadRecord.toolCall, "TOOL_CALL payload.toolCall");
1209
+ const tool = expectRecord(toolCall.tool, "TOOL_CALL payload.toolCall.tool");
1066
1210
  return {
1067
- ...typeof toolCall.id === "string" ? { id: toolCall.id } : {},
1068
- name,
1069
- arguments: parseToolArguments(toolCall.args),
1070
- status: normalizeToolStatus(toolCall.status ?? payload?.status ?? "running"),
1071
- ...result !== void 0 ? { result } : {}
1211
+ id: expectString(toolCall.id, "TOOL_CALL payload.toolCall.id"),
1212
+ name: expectToolName(tool, "TOOL_CALL payload.toolCall.tool.id"),
1213
+ arguments: expectToolArguments(toolCall.args, "TOOL_CALL payload.toolCall.args"),
1214
+ status: toolCall.status === void 0 ? "running" : expectToolStatus(toolCall.status, "TOOL_CALL payload.toolCall.status"),
1215
+ ...toolCall.output !== void 0 ? { result: toolCall.output } : {}
1072
1216
  };
1073
1217
  };
1074
- var extractLiveToolResultUpdate = (payload) => {
1075
- const tool = payload?.tool;
1076
- const result = payload?.projectedOutput !== void 0 ? payload.projectedOutput : payload?.output !== void 0 ? payload.output : payload?.content;
1218
+ var extractLiveToolResultUpdate = (payload, now = () => Date.now()) => {
1219
+ const payloadRecord = expectRecord(payload, "TOOL_RESULT payload");
1220
+ const tool = expectRecord(payloadRecord.tool, "TOOL_RESULT payload.tool");
1221
+ const result = payloadRecord.projectedOutput !== void 0 ? payloadRecord.projectedOutput : payloadRecord.output;
1222
+ if (result === void 0) fail("TOOL_RESULT payload requires output or projectedOutput");
1077
1223
  return {
1078
- ...typeof payload?.toolCallId === "string" ? { id: payload.toolCallId } : {},
1079
- ...typeof tool?.name === "string" ? { name: tool.name } : typeof tool?.id === "string" ? { name: tool.id } : {},
1080
- status: normalizeToolStatus(payload?.status),
1081
- ...result !== void 0 ? { result } : {},
1082
- endTime: nowTs()
1224
+ id: expectString(payloadRecord.toolCallId, "TOOL_RESULT payload.toolCallId"),
1225
+ name: expectToolName(tool, "TOOL_RESULT payload.tool.id"),
1226
+ status: expectToolStatus(payloadRecord.status, "TOOL_RESULT payload.status"),
1227
+ result,
1228
+ endTime: now()
1083
1229
  };
1084
1230
  };
1085
1231
  var extractToolCallsFromServerMessage = (msg) => {
1086
- const metadata = msg.metadata ?? void 0;
1087
- const topLevelToolCalls = Array.isArray(msg.toolCalls) ? msg.toolCalls || [] : [];
1088
- const metadataToolCalls = Array.isArray(metadata?.toolCalls) ? metadata.toolCalls : [];
1232
+ const metadata = msg.metadata === null || msg.metadata === void 0 ? void 0 : expectRecord(msg.metadata, "message.metadata");
1233
+ const topLevelToolCalls = readToolCallArray(msg.toolCalls, "message.toolCalls");
1234
+ const metadataToolCalls = readToolCallArray(metadata?.toolCalls, "message.metadata.toolCalls");
1089
1235
  const usedMetadataIndexes = /* @__PURE__ */ new Set();
1090
1236
  const parsed = [];
1091
- const extractToolName = (obj) => {
1092
- if (typeof obj.name === "string") return obj.name;
1093
- const t = obj.tool;
1094
- if (typeof t?.name === "string") return t.name;
1095
- if (typeof t?.id === "string") return t.id;
1096
- return void 0;
1097
- };
1098
1237
  const findMatchingMetadataIndex = (toolCall) => {
1099
- const id = typeof toolCall.id === "string" ? toolCall.id : void 0;
1100
- const name = extractToolName(toolCall);
1101
- const byId = id ? metadataToolCalls.findIndex((candidate, idx) => !usedMetadataIndexes.has(idx) && candidate?.id === id) : -1;
1102
- if (byId >= 0) return byId;
1103
- return name ? metadataToolCalls.findIndex((candidate, idx) => !usedMetadataIndexes.has(idx) && extractToolName(candidate) === name) : -1;
1238
+ const id = expectString(toolCall.id, "message.toolCalls[].id");
1239
+ return metadataToolCalls.findIndex(
1240
+ (candidate, idx) => !usedMetadataIndexes.has(idx) && candidate.id === id
1241
+ );
1104
1242
  };
1105
1243
  const parseToolCall = (primary, secondary) => {
1106
- const id = typeof primary.id === "string" ? primary.id : typeof secondary?.id === "string" ? secondary.id : void 0;
1107
- const toolObj = primary.tool;
1108
- const secondaryToolObj = secondary?.tool;
1109
- const name = typeof primary.name === "string" ? primary.name : typeof toolObj?.name === "string" ? toolObj.name : typeof toolObj?.id === "string" ? toolObj.id : typeof secondary?.name === "string" ? secondary.name : typeof secondaryToolObj?.name === "string" ? secondaryToolObj.name : typeof secondaryToolObj?.id === "string" ? secondaryToolObj.id : "tool";
1110
- const argsRaw = primary.args ?? primary.arguments ?? secondary?.args ?? secondary?.arguments;
1111
- const result = primary.output !== void 0 ? primary.output : primary.result !== void 0 ? primary.result : secondary?.output !== void 0 ? secondary.output : secondary?.result;
1112
- const status = normalizeToolStatus(primary.status ?? secondary?.status);
1244
+ const id = expectString(primary.id ?? secondary?.id, "toolCall.id");
1245
+ const tool = expectRecord(primary.tool ?? secondary?.tool, "toolCall.tool");
1246
+ const name = expectToolName(tool, "toolCall.tool.id");
1247
+ const argsRaw = primary.args ?? secondary?.args;
1248
+ const result = primary.output !== void 0 ? primary.output : secondary?.output !== void 0 ? secondary.output : primary.projectedOutput !== void 0 ? primary.projectedOutput : secondary?.projectedOutput;
1249
+ const rawStatus = primary.status ?? secondary?.status;
1113
1250
  return {
1114
- ...id ? { id } : {},
1251
+ id,
1115
1252
  name,
1116
- arguments: parseToolArguments(argsRaw),
1253
+ arguments: expectToolArguments(argsRaw, "toolCall.args"),
1117
1254
  ...result !== void 0 ? { result } : {},
1118
- status
1255
+ status: rawStatus === void 0 ? "running" : expectToolStatus(rawStatus, "toolCall.status")
1119
1256
  };
1120
1257
  };
1121
1258
  topLevelToolCalls.forEach((toolCall) => {
@@ -1130,20 +1267,24 @@ var extractToolCallsFromServerMessage = (msg) => {
1130
1267
  });
1131
1268
  return parsed;
1132
1269
  };
1133
- var extractToolResultUpdateFromMessage = (msg) => {
1270
+ var readToolCallArray = (value, path) => {
1271
+ if (value === null || value === void 0) return [];
1272
+ if (!Array.isArray(value)) fail(`${path} must be an array`);
1273
+ return value.map((toolCall, index) => expectRecord(toolCall, `${path}[${index}]`));
1274
+ };
1275
+ var extractToolResultUpdateFromMessage = (msg, now = () => Date.now()) => {
1134
1276
  if (msg.senderType !== "tool") return null;
1135
1277
  const toolCalls = extractToolCallsFromServerMessage(msg);
1136
- if (!Array.isArray(toolCalls) || toolCalls.length === 0) return null;
1278
+ if (toolCalls.length === 0) fail("tool message requires metadata.toolCalls");
1137
1279
  const firstToolCall = toolCalls[0];
1138
- const metadata = msg.metadata ?? void 0;
1139
- const fallbackResult = metadata?.output;
1140
- const result = firstToolCall.result !== void 0 ? firstToolCall.result : fallbackResult;
1280
+ if (firstToolCall.result === void 0) fail("tool result message requires tool call output");
1281
+ expectStringValue(msg.createdAt, "tool result message.createdAt");
1141
1282
  return {
1142
- ...firstToolCall.id ? { id: firstToolCall.id } : {},
1143
- ...firstToolCall.name ? { name: firstToolCall.name } : {},
1144
- ...result !== void 0 ? { result } : {},
1283
+ id: firstToolCall.id,
1284
+ name: firstToolCall.name,
1285
+ result: firstToolCall.result,
1145
1286
  status: firstToolCall.status,
1146
- endTime: msg.createdAt ? new Date(msg.createdAt).getTime() : nowTs()
1287
+ endTime: new Date(msg.createdAt).getTime()
1147
1288
  };
1148
1289
  };
1149
1290
  var mergePersistedToolResults = (messages, updates) => {
@@ -1166,42 +1307,95 @@ var prependUniqueMessages = (olderMessages, currentMessages) => {
1166
1307
  }
1167
1308
  return combined;
1168
1309
  };
1169
- var convertServerMessage = (msg) => {
1170
- const timestamp = msg.createdAt ? new Date(msg.createdAt).getTime() : nowTs();
1171
- const metadata = msg.metadata ?? void 0;
1172
- const attachmentsMeta = Array.isArray(metadata?.attachments) ? metadata.attachments : [];
1173
- const attachments = attachmentsMeta.flatMap((att) => {
1174
- const kind = typeof att.kind === "string" ? att.kind : void 0;
1175
- const dataUrl = typeof att.dataUrl === "string" ? att.dataUrl : void 0;
1176
- const mimeType = typeof att.mimeType === "string" ? att.mimeType : void 0;
1177
- if (!dataUrl) return [];
1178
- if (kind === "image") {
1179
- return [{ kind: "image", dataUrl, mimeType: mimeType ?? "image/jpeg" }];
1180
- }
1181
- if (kind === "audio") {
1182
- return [{
1183
- kind: "audio",
1184
- dataUrl,
1185
- mimeType: mimeType ?? "audio/webm",
1186
- durationMs: typeof att.durationMs === "number" ? att.durationMs : void 0
1187
- }];
1188
- }
1189
- if (kind === "video") {
1190
- return [{
1191
- kind: "video",
1192
- dataUrl,
1193
- mimeType: mimeType ?? "video/mp4",
1194
- durationMs: typeof att.durationMs === "number" ? att.durationMs : void 0,
1195
- poster: typeof att.poster === "string" ? att.poster : void 0
1196
- }];
1197
- }
1198
- return [];
1310
+ var messageAgentKey = (message) => {
1311
+ if (message.role !== "assistant") return null;
1312
+ if (message.sender?.type === "agent" || message.sender?.type === "tool") {
1313
+ return message.sender.agentId ?? message.sender.id;
1314
+ }
1315
+ return null;
1316
+ };
1317
+ var canAttachToStreamingAssistant = (message, incomingAgentKey) => {
1318
+ if (!message || message.role !== "assistant" || !message.isStreaming) {
1319
+ return false;
1320
+ }
1321
+ const currentAgentKey = messageAgentKey(message);
1322
+ return !incomingAgentKey || !currentAgentKey || currentAgentKey === incomingAgentKey;
1323
+ };
1324
+
1325
+ // src/messageContract.ts
1326
+ var isInternalMessageMetadata = (metadata) => metadata?.visibility === "internal";
1327
+ var defaultCreateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1328
+ var defaultNow = () => Date.now();
1329
+ var roleBySender = {
1330
+ user: "user",
1331
+ agent: "assistant",
1332
+ tool: "assistant",
1333
+ system: "system"
1334
+ };
1335
+ var expectNumber = (value, path) => {
1336
+ if (typeof value !== "number" || Number.isNaN(value)) throw new ContractViolation(`${path} must be a number`);
1337
+ return value;
1338
+ };
1339
+ var extractAttachments = (metadata) => {
1340
+ if (metadata?.attachments === void 0) return [];
1341
+ if (!Array.isArray(metadata.attachments)) {
1342
+ throw new ContractViolation("message.metadata.attachments must be an array");
1343
+ }
1344
+ return metadata.attachments.map((value, index) => {
1345
+ const path = `message.metadata.attachments[${index}]`;
1346
+ const att = expectRecord(value, path);
1347
+ const base = {
1348
+ kind: expectString(att.kind, `${path}.kind`),
1349
+ dataUrl: expectString(att.dataUrl, `${path}.dataUrl`),
1350
+ mimeType: expectString(att.mimeType, `${path}.mimeType`)
1351
+ };
1352
+ if (base.kind === "image") return base;
1353
+ if (base.kind === "audio") return {
1354
+ ...base,
1355
+ ...att.durationMs !== void 0 ? { durationMs: expectNumber(att.durationMs, `${path}.durationMs`) } : {}
1356
+ };
1357
+ if (base.kind === "video") return {
1358
+ ...base,
1359
+ ...att.durationMs !== void 0 ? { durationMs: expectNumber(att.durationMs, `${path}.durationMs`) } : {},
1360
+ ...att.poster !== void 0 ? { poster: expectString(att.poster, `${path}.poster`) } : {}
1361
+ };
1362
+ throw new ContractViolation(`${path}.kind must be image, audio, or video`);
1199
1363
  });
1200
- const role = msg.senderType === "agent" ? "assistant" : msg.senderType === "user" ? "user" : "assistant";
1364
+ };
1365
+ var assertRestMessageContract = (msg) => {
1366
+ expectString(msg.id, "message.id");
1367
+ expectString(msg.threadId, "message.threadId");
1368
+ if (!(msg.senderType in roleBySender)) throw new ContractViolation("message.senderType must be user, agent, tool, or system");
1369
+ expectStringValue(msg.content, "message.content");
1370
+ if (msg.metadata !== void 0 && msg.metadata !== null) expectRecord(msg.metadata, "message.metadata");
1371
+ if (msg.createdAt !== void 0) expectString(msg.createdAt, "message.createdAt");
1372
+ };
1373
+ var shouldRenderHydratedMessage = (msg) => {
1374
+ assertRestMessageContract(msg);
1375
+ const meta = msg.metadata ?? {};
1376
+ if (isInternalMessageMetadata(meta)) {
1377
+ return false;
1378
+ }
1379
+ const text = expectStringValue(msg.content, "message.content").trim();
1380
+ const hasText = text.length > 0;
1381
+ const hasToolCalls = extractToolCallsFromServerMessage(msg).length > 0;
1382
+ const hasAttachments = extractAttachments(meta).length > 0;
1383
+ if (msg.senderType === "tool") {
1384
+ return hasAttachments;
1385
+ }
1386
+ return hasText || hasToolCalls || hasAttachments;
1387
+ };
1388
+ var convertServerMessage = (msg, options = {}) => {
1389
+ assertRestMessageContract(msg);
1390
+ const timestamp = msg.createdAt ? new Date(msg.createdAt).getTime() : (options.now ?? defaultNow)();
1391
+ const metadata = msg.metadata ?? void 0;
1392
+ const attachments = extractAttachments(metadata);
1393
+ const messageContent = expectStringValue(msg.content, "message.content");
1394
+ const role = roleBySender[msg.senderType];
1201
1395
  const parsedToolCalls = extractToolCallsFromServerMessage(msg);
1202
1396
  const shouldRenderToolCalls = msg.senderType !== "tool";
1203
1397
  const mappedToolCalls = parsedToolCalls.map((toolCall) => ({
1204
- id: toolCall.id ?? generateId(),
1398
+ id: toolCall.id ?? (options.createId ?? defaultCreateId)(),
1205
1399
  name: toolCall.name,
1206
1400
  arguments: toolCall.arguments,
1207
1401
  status: toolCall.status,
@@ -1209,11 +1403,31 @@ var convertServerMessage = (msg) => {
1209
1403
  }));
1210
1404
  const hasToolCalls = shouldRenderToolCalls && mappedToolCalls.length > 0;
1211
1405
  const isToolSender = msg.senderType === "tool";
1212
- const content = isToolSender ? "" : (msg.content ?? "") || (hasToolCalls ? "" : "");
1406
+ const content = isToolSender ? "" : messageContent;
1213
1407
  const reasoning = typeof msg.reasoning === "string" && msg.reasoning.length > 0 ? msg.reasoning : void 0;
1214
- const senderAgentId = msg.senderType === "agent" ? msg.senderId ?? void 0 : void 0;
1215
- const senderName = msg.senderType === "agent" ? typeof msg.senderName === "string" ? msg.senderName : msg.senderId ?? void 0 : void 0;
1216
- return syncAssistantActivity({
1408
+ const activityItems = [
1409
+ ...reasoning ? [{
1410
+ id: `${msg.id}:thinking`,
1411
+ kind: "thinking",
1412
+ status: "complete",
1413
+ completedAt: timestamp,
1414
+ details: { reasoning }
1415
+ }] : [],
1416
+ ...hasToolCalls ? mappedToolCalls.map((toolCall) => ({
1417
+ id: toolCall.id,
1418
+ kind: "tool",
1419
+ status: toolCall.status === "failed" ? "failed" : toolCall.status === "completed" ? "complete" : "active",
1420
+ toolName: toolCall.name,
1421
+ startedAt: toolCall.startTime,
1422
+ completedAt: toolCall.endTime,
1423
+ details: {
1424
+ toolCall,
1425
+ ...toolCall.result !== void 0 ? { result: toolCall.result } : {}
1426
+ }
1427
+ })) : []
1428
+ ];
1429
+ const sender = resolveHydratedMessageSender(msg, options.senderOptions);
1430
+ return {
1217
1431
  id: msg.id,
1218
1432
  role,
1219
1433
  content,
@@ -1222,14 +1436,49 @@ var convertServerMessage = (msg) => {
1222
1436
  isStreaming: false,
1223
1437
  isComplete: true,
1224
1438
  metadata,
1225
- _activityToolCalls: hasToolCalls ? mappedToolCalls : void 0,
1226
- ...reasoning ? { _activityReasoning: reasoning } : {},
1227
- ...senderAgentId ? { senderAgentId } : {},
1228
- ...senderName ? { senderName } : {}
1439
+ activity: activityItems.length > 0 ? { items: activityItems } : void 0,
1440
+ sender
1441
+ };
1442
+ };
1443
+ var prepareHydratedMessages = async (rawMessages, options = {}) => {
1444
+ rawMessages.forEach(assertRestMessageContract);
1445
+ const resolvedMessages = await resolveAssetsInMessages(rawMessages);
1446
+ resolvedMessages.forEach((msg) => {
1447
+ if (msg.senderType === "tool") {
1448
+ const metadata = msg.metadata ?? void 0;
1449
+ if (!metadata) {
1450
+ throw new ContractViolation("tool message requires metadata");
1451
+ }
1452
+ options.onToolOutput?.(metadata.output === void 0 ? metadata : { output: metadata.output });
1453
+ }
1229
1454
  });
1455
+ const now = options.now ?? defaultNow;
1456
+ const toolResultUpdates = resolvedMessages.map((msg) => extractToolResultUpdateFromMessage(msg, now)).filter((update) => update !== null);
1457
+ const viewMessages = resolvedMessages.filter(shouldRenderHydratedMessage).map((msg) => convertServerMessage(msg, options));
1458
+ return {
1459
+ viewMessages,
1460
+ toolResultUpdates
1461
+ };
1230
1462
  };
1463
+
1464
+ // src/useCopilotzChat.ts
1465
+ var nowTs = () => Date.now();
1466
+ var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1467
+ var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1468
+ var getEventPayload = (event) => event?.payload ?? event;
1469
+ var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
1470
+ var THREAD_MESSAGES_PAGE_SIZE = 50;
1471
+ var createEmptyMessagePageInfo = () => ({
1472
+ hasMoreBefore: false,
1473
+ oldestMessageId: null,
1474
+ newestMessageId: null
1475
+ });
1231
1476
  function useCopilotz({
1232
1477
  userId,
1478
+ userName,
1479
+ userAvatar,
1480
+ assistantName,
1481
+ agentOptions = [],
1233
1482
  initialContext,
1234
1483
  bootstrap,
1235
1484
  defaultThreadName,
@@ -1269,6 +1518,11 @@ function useCopilotz({
1269
1518
  const userContextSeedRef = useRef2(userContextSeed);
1270
1519
  const messagePageInfoRef = useRef2(messagePageInfo);
1271
1520
  const isLoadingOlderMessagesRef = useRef2(isLoadingOlderMessages);
1521
+ const senderOptionsRef = useRef2({
1522
+ agents: agentOptions,
1523
+ user: userId ? { id: userId, name: userName, avatarUrl: userAvatar } : null,
1524
+ assistantName
1525
+ });
1272
1526
  const persistedToolUpdatesRef = useRef2([]);
1273
1527
  const liveToolUpdatesRef = useRef2([]);
1274
1528
  threadsRef.current = threads;
@@ -1279,6 +1533,11 @@ function useCopilotz({
1279
1533
  userContextSeedRef.current = userContextSeed;
1280
1534
  messagePageInfoRef.current = messagePageInfo;
1281
1535
  isLoadingOlderMessagesRef.current = isLoadingOlderMessages;
1536
+ senderOptionsRef.current = {
1537
+ agents: agentOptions,
1538
+ user: userId ? { id: userId, name: userName, avatarUrl: userAvatar } : null,
1539
+ assistantName
1540
+ };
1282
1541
  preferredAgentRef.current = preferredAgentName ?? null;
1283
1542
  participantsRef.current = participants ?? null;
1284
1543
  targetAgentNameRef.current = targetAgentName ?? null;
@@ -1335,20 +1594,20 @@ function useCopilotz({
1335
1594
  }
1336
1595
  const senderType = getEventSenderType(payload);
1337
1596
  if (senderType !== "agent" || typeof payload.content !== "string") return;
1338
- const agentIdentity = resolveLiveAgentIdentity(event);
1339
- const incomingAgentKey = agentIdentity.senderAgentId ?? agentIdentity.senderName ?? null;
1597
+ const sender = resolveLiveEventSender(event, senderOptionsRef.current);
1598
+ const incomingAgentKey = sender.agentId ?? sender.id;
1340
1599
  setMessages((prev) => {
1341
1600
  const next = [...prev];
1342
1601
  for (let i = next.length - 1; i >= 0; i--) {
1343
1602
  const m = next[i];
1344
1603
  if (canAttachToStreamingAssistant(m, incomingAgentKey)) {
1345
- next[i] = syncAssistantActivity({
1604
+ next[i] = {
1346
1605
  ...m,
1347
1606
  content: payload.content,
1348
1607
  isStreaming: false,
1349
1608
  isComplete: true,
1350
- ...agentIdentity
1351
- });
1609
+ sender
1610
+ };
1352
1611
  return next;
1353
1612
  }
1354
1613
  }
@@ -1358,7 +1617,7 @@ function useCopilotz({
1358
1617
  }
1359
1618
  return [
1360
1619
  ...next,
1361
- syncAssistantActivity({
1620
+ {
1362
1621
  id: generateId(),
1363
1622
  role: "assistant",
1364
1623
  content: payload.content,
@@ -1366,8 +1625,8 @@ function useCopilotz({
1366
1625
  isStreaming: false,
1367
1626
  isComplete: true,
1368
1627
  metadata: liveMetadata,
1369
- ...agentIdentity
1370
- })
1628
+ sender
1629
+ }
1371
1630
  ];
1372
1631
  });
1373
1632
  }, []);
@@ -1424,33 +1683,12 @@ function useCopilotz({
1424
1683
  }
1425
1684
  }, [updateThreadsState, getRequestHeaders]);
1426
1685
  const prepareThreadMessages = useCallback2(async (rawMessages) => {
1427
- const resolvedMessages = await resolveAssetsInMessages(rawMessages);
1428
- resolvedMessages.forEach((msg) => {
1429
- if (msg.senderType === "tool") {
1430
- const metadata = msg.metadata;
1431
- const output = metadata?.output ?? metadata;
1432
- if (output) processToolOutput(output);
1433
- }
1686
+ return prepareHydratedMessages(rawMessages, {
1687
+ senderOptions: senderOptionsRef.current,
1688
+ createId: generateId,
1689
+ now: nowTs,
1690
+ onToolOutput: processToolOutput
1434
1691
  });
1435
- const toolResultUpdates = resolvedMessages.map((msg) => extractToolResultUpdateFromMessage(msg)).filter((update) => update !== null);
1436
- const viewMessages = resolvedMessages.filter((msg) => {
1437
- const meta = msg.metadata ?? {};
1438
- if (isInternalMessageMetadata(meta)) {
1439
- return false;
1440
- }
1441
- const text = (typeof msg.content === "string" ? msg.content : "").trim();
1442
- const hasText = text.length > 0;
1443
- const hasToolCalls = extractToolCallsFromServerMessage(msg).length > 0;
1444
- const hasAttachments = Array.isArray(meta.attachments) && meta.attachments.length > 0;
1445
- if (msg.senderType === "tool") {
1446
- return hasAttachments;
1447
- }
1448
- return hasText || hasToolCalls || hasAttachments;
1449
- }).map(convertServerMessage);
1450
- return {
1451
- viewMessages,
1452
- toolResultUpdates
1453
- };
1454
1692
  }, [processToolOutput]);
1455
1693
  const loadThreadMessages = useCallback2(async (threadId) => {
1456
1694
  const requestId = messagesRequestRef.current + 1;
@@ -1658,14 +1896,14 @@ function useCopilotz({
1658
1896
  dataUrl,
1659
1897
  mimeType
1660
1898
  };
1661
- setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? syncAssistantActivity({
1899
+ setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? {
1662
1900
  ...msg,
1663
1901
  attachments: [...msg.attachments || [], mediaAttachment]
1664
- }) : msg));
1902
+ } : msg));
1665
1903
  }, []);
1666
1904
  const sendCopilotzMessage = useCallback2(async (params) => {
1667
- let currentAssistantId = generateId();
1668
- let currentAssistantIdentity = {};
1905
+ let currentAssistantId = params.assistantMessageId ?? generateId();
1906
+ let currentAssistantSender = params.assistantSender;
1669
1907
  params.onBeforeStart?.(currentAssistantId);
1670
1908
  let hasStreamProgress = false;
1671
1909
  const updateStreamingMessage = (partial, opts) => {
@@ -1673,28 +1911,26 @@ function useCopilotz({
1673
1911
  hasStreamProgress = true;
1674
1912
  }
1675
1913
  const isReasoning = opts?.isReasoning ?? false;
1676
- const nextIdentity = normalizeAgentIdentity(opts?.agent ?? null);
1677
- if (nextIdentity.senderAgentId || nextIdentity.senderName) {
1678
- currentAssistantIdentity = {
1679
- ...currentAssistantIdentity,
1680
- ...nextIdentity
1681
- };
1914
+ const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
1915
+ if (nextSender) {
1916
+ currentAssistantSender = nextSender;
1682
1917
  }
1683
- const agentIdentity = currentAssistantIdentity;
1684
- const nextAgentKey = agentIdentity.senderAgentId ?? agentIdentity.senderName ?? null;
1918
+ const nextAgentKey = currentAssistantSender?.agentId ?? currentAssistantSender?.id ?? null;
1685
1919
  const applyUpdate = (msg) => {
1686
- return updateAssistantMessageToken(msg, {
1687
- partial,
1688
- isReasoning,
1689
- agentIdentity
1690
- });
1920
+ return {
1921
+ ...updateAssistantMessageToken(msg, {
1922
+ partial,
1923
+ isReasoning
1924
+ }),
1925
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1926
+ };
1691
1927
  };
1692
1928
  setMessages((prev) => {
1693
1929
  const idx = prev.findIndex((m) => m.id === currentAssistantId);
1694
1930
  if (idx >= 0 && canAttachToStreamingAssistant(prev[idx], nextAgentKey)) {
1695
1931
  const msg = prev[idx];
1696
1932
  const next = applyUpdate(msg);
1697
- if (msg.content === next.content && msg._activityReasoning === next._activityReasoning && msg._activityReasoningStreaming === next._activityReasoningStreaming && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
1933
+ if (msg.content === next.content && msg.activity === next.activity && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
1698
1934
  return prev;
1699
1935
  }
1700
1936
  const updated = [...prev];
@@ -1705,7 +1941,7 @@ function useCopilotz({
1705
1941
  if (canAttachToStreamingAssistant(last, nextAgentKey)) {
1706
1942
  currentAssistantId = last.id;
1707
1943
  const next = applyUpdate(last);
1708
- if (last.content === next.content && last._activityReasoning === next._activityReasoning && last._activityReasoningStreaming === next._activityReasoningStreaming && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
1944
+ if (last.content === next.content && last.activity === next.activity && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
1709
1945
  return prev;
1710
1946
  }
1711
1947
  const updated = [...prev];
@@ -1723,7 +1959,7 @@ function useCopilotz({
1723
1959
  timestamp: nowTs(),
1724
1960
  isStreaming: true,
1725
1961
  isComplete: false,
1726
- ...currentAssistantIdentity
1962
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1727
1963
  };
1728
1964
  return [...prev, applyUpdate(base)];
1729
1965
  }
@@ -1770,7 +2006,7 @@ function useCopilotz({
1770
2006
  if (fallbackIdx < 0) return prev;
1771
2007
  const message = prev[fallbackIdx];
1772
2008
  const nextMessage = finalizeAssistantMessage(message, finalAnswer);
1773
- if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message._activityReasoningStreaming === nextMessage._activityReasoningStreaming) {
2009
+ if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity) {
1774
2010
  return prev;
1775
2011
  }
1776
2012
  const updated = [...prev];
@@ -1785,7 +2021,6 @@ function useCopilotz({
1785
2021
  const payload = event?.payload ?? event;
1786
2022
  if (type === "TOOL_CALL") {
1787
2023
  const parsedToolCall = extractLiveToolCall(payload);
1788
- if (!parsedToolCall) return null;
1789
2024
  return {
1790
2025
  id: generateId(),
1791
2026
  threadId: curThreadId ?? "",
@@ -1871,14 +2106,9 @@ function useCopilotz({
1871
2106
  const parsedToolCall = extractLiveToolCall(
1872
2107
  payload ?? {}
1873
2108
  );
1874
- if (!parsedToolCall) return;
1875
- const eventAgentIdentity = resolveLiveAgentIdentity(event);
1876
- if (eventAgentIdentity.senderAgentId || eventAgentIdentity.senderName) {
1877
- currentAssistantIdentity = {
1878
- ...currentAssistantIdentity,
1879
- ...eventAgentIdentity
1880
- };
1881
- }
2109
+ const eventSender = resolveLiveEventSender(event, senderOptionsRef.current);
2110
+ currentAssistantSender = eventSender;
2111
+ const eventAgentKey = currentAssistantSender.agentId ?? currentAssistantSender.id;
1882
2112
  const callId = parsedToolCall.id ?? generateId();
1883
2113
  const toolName = parsedToolCall.name;
1884
2114
  const bufferedUpdates = liveToolUpdatesRef.current;
@@ -1892,6 +2122,10 @@ function useCopilotz({
1892
2122
  const endTime = bufferedUpdate?.endTime;
1893
2123
  setMessages(
1894
2124
  (prev) => (() => {
2125
+ const canHostActivity = (message) => {
2126
+ if (!message) return false;
2127
+ return message.role === "assistant" && message.isStreaming && message.content.trim().length === 0 && !message.attachments?.length;
2128
+ };
1895
2129
  const appendToolCall = (msg) => ({
1896
2130
  ...appendAssistantToolCall(msg, {
1897
2131
  id: callId,
@@ -1903,21 +2137,21 @@ function useCopilotz({
1903
2137
  ...endTime !== void 0 ? { endTime } : {}
1904
2138
  })
1905
2139
  });
1906
- const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming);
2140
+ const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming && canHostActivity(message));
1907
2141
  if (currentIdx >= 0) {
1908
2142
  const next = [...prev];
1909
2143
  next[currentIdx] = appendToolCall({
1910
2144
  ...next[currentIdx],
1911
2145
  isStreaming: true,
1912
2146
  isComplete: false,
1913
- ...currentAssistantIdentity
2147
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1914
2148
  });
1915
2149
  return next;
1916
2150
  }
1917
2151
  const last = prev[prev.length - 1];
1918
- if (canAttachToStreamingAssistant(
2152
+ if (canHostActivity(last) && canAttachToStreamingAssistant(
1919
2153
  last,
1920
- currentAssistantIdentity.senderAgentId ?? currentAssistantIdentity.senderName ?? null
2154
+ eventAgentKey
1921
2155
  )) {
1922
2156
  currentAssistantId = last.id;
1923
2157
  const next = [...prev];
@@ -1925,7 +2159,7 @@ function useCopilotz({
1925
2159
  ...last,
1926
2160
  isStreaming: true,
1927
2161
  isComplete: false,
1928
- ...currentAssistantIdentity
2162
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1929
2163
  });
1930
2164
  return next;
1931
2165
  }
@@ -1940,7 +2174,7 @@ function useCopilotz({
1940
2174
  timestamp: nowTs(),
1941
2175
  isStreaming: true,
1942
2176
  isComplete: false,
1943
- ...currentAssistantIdentity
2177
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1944
2178
  })
1945
2179
  ];
1946
2180
  })()
@@ -1950,7 +2184,11 @@ function useCopilotz({
1950
2184
  }
1951
2185
  const sm = await toServerMessageFromEvent(event);
1952
2186
  if (sm) {
1953
- const viewMsg = convertServerMessage(sm);
2187
+ const viewMsg = convertServerMessage(sm, {
2188
+ senderOptions: senderOptionsRef.current,
2189
+ createId: generateId,
2190
+ now: nowTs
2191
+ });
1954
2192
  finalizeCurrentAssistantBubble();
1955
2193
  setMessages((prev) => [...prev, viewMsg]);
1956
2194
  return;
@@ -2008,8 +2246,19 @@ function useCopilotz({
2008
2246
  content,
2009
2247
  timestamp,
2010
2248
  attachments: attachments.length > 0 ? attachments : void 0,
2011
- isComplete: true
2249
+ isComplete: true,
2250
+ sender: resolveUserSender({
2251
+ id: userId,
2252
+ name: userContextSeedRef.current?.profile?.full_name ?? userId
2253
+ })
2012
2254
  };
2255
+ const assistantSender = targetAgentNameRef.current ? resolveAgentSender(
2256
+ { id: targetAgentNameRef.current, name: targetAgentNameRef.current },
2257
+ senderOptionsRef.current
2258
+ ) : preferredAgentRef.current ? resolveAgentSender(
2259
+ { id: preferredAgentRef.current, name: preferredAgentRef.current },
2260
+ senderOptionsRef.current
2261
+ ) : resolveAssistantFallbackSender(senderOptionsRef.current);
2013
2262
  const assistantPlaceholder = {
2014
2263
  id: generateId(),
2015
2264
  role: "assistant",
@@ -2017,9 +2266,9 @@ function useCopilotz({
2017
2266
  timestamp: timestamp + 1,
2018
2267
  isStreaming: true,
2019
2268
  isComplete: false,
2020
- ...targetAgentNameRef.current ? { senderName: targetAgentNameRef.current } : {}
2269
+ sender: assistantSender
2021
2270
  };
2022
- setMessages((prev) => [...prev, userMessage, syncAssistantActivity(assistantPlaceholder)]);
2271
+ setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);
2023
2272
  setSpecialState(null);
2024
2273
  if (!threadsRef.current.some((t) => t.id === conversationKey)) {
2025
2274
  const newThread = {
@@ -2043,6 +2292,8 @@ function useCopilotz({
2043
2292
  // userName can be anything, but let's try to find it in context or just fallback
2044
2293
  userName: userContextSeedRef.current?.profile?.full_name ?? userId,
2045
2294
  agentName: preferredAgentRef.current,
2295
+ assistantMessageId: assistantPlaceholder.id,
2296
+ assistantSender,
2046
2297
  // Include pending title for new threads
2047
2298
  threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
2048
2299
  });
@@ -2066,24 +2317,26 @@ function useCopilotz({
2066
2317
  const message = finalized[i];
2067
2318
  if (message.role !== "assistant") continue;
2068
2319
  const updated = [...finalized];
2069
- updated[i] = syncAssistantActivity({
2320
+ updated[i] = {
2070
2321
  ...message,
2071
2322
  content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2072
2323
  isStreaming: false,
2073
- isComplete: true
2074
- });
2324
+ isComplete: true,
2325
+ sender: message.sender ?? resolveAssistantFallbackSender(senderOptionsRef.current)
2326
+ };
2075
2327
  return updated;
2076
2328
  }
2077
2329
  return [
2078
2330
  ...finalized,
2079
- syncAssistantActivity({
2331
+ {
2080
2332
  id: generateId(),
2081
2333
  role: "assistant",
2082
2334
  content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2083
2335
  timestamp: nowTs(),
2084
2336
  isStreaming: false,
2085
- isComplete: true
2086
- })
2337
+ isComplete: true,
2338
+ sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2339
+ }
2087
2340
  ];
2088
2341
  });
2089
2342
  }
@@ -2122,14 +2375,15 @@ function useCopilotz({
2122
2375
  return;
2123
2376
  }
2124
2377
  setMessages([
2125
- syncAssistantActivity({
2378
+ {
2126
2379
  id: generateId(),
2127
2380
  role: "assistant",
2128
2381
  content: "N\xE3o foi poss\xEDvel iniciar a conversa. Tente novamente mais tarde.",
2129
2382
  timestamp: nowTs(),
2130
2383
  isStreaming: false,
2131
- isComplete: true
2132
- })
2384
+ isComplete: true,
2385
+ sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2386
+ }
2133
2387
  ]);
2134
2388
  }
2135
2389
  }, [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName, getSpecialStateFromError]);
@@ -2275,6 +2529,10 @@ var CopilotzChat = ({
2275
2529
  loadOlderMessages
2276
2530
  } = useCopilotz({
2277
2531
  userId,
2532
+ userName,
2533
+ userAvatar,
2534
+ assistantName: userConfig?.branding?.title,
2535
+ agentOptions,
2278
2536
  initialContext,
2279
2537
  bootstrap,
2280
2538
  defaultThreadName: userConfig?.labels?.defaultThreadName,