@copilotz/chat-adapter 0.7.1 → 0.7.3

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
@@ -424,10 +424,17 @@ async function runCopilotzStream(options) {
424
424
  let buffer = "";
425
425
  let aggregatedText = "";
426
426
  let aggregatedReasoning = "";
427
+ let lastCompletedText = "";
427
428
  let lastTokenWasReasoning = false;
428
429
  let hadNonReasoningContent = false;
429
430
  const collectedMessages = [];
430
431
  let collectedMedia = null;
432
+ const resetTokenAggregation = () => {
433
+ aggregatedText = "";
434
+ aggregatedReasoning = "";
435
+ lastTokenWasReasoning = false;
436
+ hadNonReasoningContent = false;
437
+ };
431
438
  const processEvent = (eventChunk) => {
432
439
  if (!eventChunk.trim()) return;
433
440
  const lines = eventChunk.split("\n");
@@ -475,6 +482,12 @@ async function runCopilotzStream(options) {
475
482
  if (chunk || isComplete) {
476
483
  const tokenText = isReasoning ? aggregatedReasoning : aggregatedText;
477
484
  onToken?.(tokenText, isComplete, payload2, { isReasoning });
485
+ if (isComplete) {
486
+ if (!isReasoning && tokenText) {
487
+ lastCompletedText = tokenText;
488
+ }
489
+ resetTokenAggregation();
490
+ }
478
491
  }
479
492
  break;
480
493
  }
@@ -493,8 +506,11 @@ async function runCopilotzStream(options) {
493
506
  }
494
507
  case "TOOL_RESULT":
495
508
  case "LLM_RESULT": {
496
- hadNonReasoningContent = true;
497
- lastTokenWasReasoning = false;
509
+ const resultAnswer = typeof payload2?.payload?.answer === "string" ? payload2.payload.answer : typeof payload2?.answer === "string" ? payload2.answer : void 0;
510
+ if (resultAnswer) {
511
+ lastCompletedText = resultAnswer;
512
+ }
513
+ resetTokenAggregation();
498
514
  onMessageEvent?.(payload2);
499
515
  break;
500
516
  }
@@ -540,7 +556,7 @@ async function runCopilotzStream(options) {
540
556
  processEvent(buffer);
541
557
  }
542
558
  return {
543
- text: aggregatedText,
559
+ text: lastCompletedText || aggregatedText,
544
560
  messages: collectedMessages,
545
561
  media: collectedMedia
546
562
  };
@@ -672,64 +688,6 @@ async function deleteThread(threadId, getRequestHeaders) {
672
688
  return true;
673
689
  }
674
690
 
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
691
  // src/useUrlState.ts
734
692
  import { useState, useEffect, useCallback, useRef } from "react";
735
693
  var DEFAULT_PARAMS = {
@@ -835,194 +793,397 @@ function useUrlState(config = {}) {
835
793
  }
836
794
 
837
795
  // src/activity.ts
838
- var isToolCallActive = (toolCall) => toolCall.status === "pending" || toolCall.status === "running";
796
+ var thinkingId = "thinking";
797
+ var answeringId = "answering";
798
+ var getItems = (message) => Array.isArray(message.activity?.items) ? message.activity.items : [];
799
+ var setItems = (message, items) => ({
800
+ ...message,
801
+ activity: items.length > 0 ? { items } : void 0
802
+ });
803
+ var toolStatusToActivityStatus = (status) => {
804
+ if (status === "failed") return "failed";
805
+ if (status === "completed") return "complete";
806
+ return "active";
807
+ };
808
+ var upsertItem = (message, item) => {
809
+ const items = getItems(message);
810
+ const index = items.findIndex((current) => current.id === item.id);
811
+ if (index === -1) return setItems(message, [...items, item]);
812
+ const next = [...items];
813
+ next[index] = {
814
+ ...next[index],
815
+ ...item,
816
+ details: {
817
+ ...next[index].details ?? {},
818
+ ...item.details ?? {}
819
+ }
820
+ };
821
+ return setItems(message, next);
822
+ };
823
+ var completeItems = (message, shouldComplete) => setItems(message, getItems(message).map((item) => item.status === "active" && shouldComplete(item) ? { ...item, status: "complete", completedAt: Date.now() } : item));
839
824
  var hasVisibleAssistantOutput = (message) => {
840
825
  if (message.role !== "assistant") return false;
841
826
  if (typeof message.content === "string" && message.content.trim().length > 0) return true;
842
827
  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
- };
828
+ return getItems(message).length > 0;
889
829
  };
890
830
  var toPublicChatMessage = (message) => {
891
- const { _activityReasoning, _activityReasoningStreaming, _activityToolCalls, ...rest } = syncAssistantActivity(message);
831
+ if (message.role === "assistant") return message;
832
+ const { activity, ...rest } = message;
892
833
  return rest;
893
834
  };
894
835
  var updateAssistantMessageToken = (message, params) => {
895
836
  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
- } : {
837
+ if (params.isReasoning) {
838
+ return upsertItem({
839
+ ...message,
840
+ isStreaming: true,
841
+ isComplete: false
842
+ }, {
843
+ id: thinkingId,
844
+ kind: "thinking",
845
+ status: "active",
846
+ startedAt: getItems(message).find((item) => item.id === thinkingId)?.startedAt ?? Date.now(),
847
+ details: { reasoning: params.partial }
848
+ });
849
+ }
850
+ return upsertItem(completeItems({
904
851
  ...message,
905
- ...params.agentIdentity,
906
852
  content: params.partial,
907
- _activityReasoningStreaming: false,
908
853
  isStreaming: true,
909
854
  isComplete: false
910
- };
911
- return syncAssistantActivity(next);
855
+ }, (item) => item.kind === "thinking" || item.kind === "tool"), {
856
+ id: answeringId,
857
+ kind: "answering",
858
+ status: "active",
859
+ startedAt: getItems(message).find((item) => item.id === answeringId)?.startedAt ?? Date.now()
860
+ });
912
861
  };
913
862
  var appendAssistantToolCall = (message, toolCall) => {
914
863
  if (message.role !== "assistant") return message;
915
- return syncAssistantActivity({
864
+ const status = toolStatusToActivityStatus(toolCall.status);
865
+ return upsertItem({
916
866
  ...message,
917
- _activityToolCalls: [
918
- ...Array.isArray(message._activityToolCalls) ? message._activityToolCalls : [],
919
- toolCall
920
- ],
921
867
  isStreaming: true,
922
868
  isComplete: false
869
+ }, {
870
+ id: toolCall.id,
871
+ kind: "tool",
872
+ status,
873
+ toolName: toolCall.name,
874
+ startedAt: toolCall.startTime ?? Date.now(),
875
+ ...status !== "active" ? { completedAt: toolCall.endTime ?? Date.now() } : {},
876
+ details: {
877
+ toolCall,
878
+ ...toolCall.result !== void 0 ? { result: toolCall.result } : {}
879
+ }
923
880
  });
924
881
  };
925
882
  var applyAssistantToolResult = (message, update) => {
926
883
  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
- });
884
+ const items = getItems(message);
885
+ const index = items.findIndex((item2) => item2.kind === "tool" && (update.id && item2.id === update.id || !update.id && item2.toolName === update.name));
886
+ if (index === -1) return message;
887
+ const item = items[index];
888
+ const toolCall = item.details?.toolCall;
889
+ const nextToolCall = toolCall ? { ...toolCall, ...update } : {
890
+ id: update.id ?? item.id,
891
+ name: update.name,
892
+ arguments: {},
893
+ status: update.status,
894
+ ...update.result !== void 0 ? { result: update.result } : {},
895
+ ...update.endTime !== void 0 ? { endTime: update.endTime } : {}
896
+ };
897
+ const status = toolStatusToActivityStatus(update.status);
898
+ const next = [...items];
899
+ next[index] = {
900
+ ...item,
901
+ status,
902
+ toolName: update.name,
903
+ ...status !== "active" ? { completedAt: update.endTime ?? Date.now() } : {},
904
+ details: {
905
+ ...item.details ?? {},
906
+ toolCall: nextToolCall,
907
+ ...update.result !== void 0 ? { result: update.result } : {}
908
+ }
909
+ };
910
+ return setItems(message, next);
941
911
  };
942
912
  var finalizeAssistantMessage = (message, finalAnswer) => {
943
913
  if (message.role !== "assistant") return message;
944
- return syncAssistantActivity({
914
+ const completed = completeItems({
945
915
  ...message,
946
916
  ...typeof finalAnswer === "string" && finalAnswer.length > 0 ? { content: finalAnswer } : {},
947
917
  isStreaming: false,
948
- isComplete: true,
949
- _activityReasoningStreaming: false
950
- });
918
+ isComplete: true
919
+ }, (item) => item.kind === "thinking" || item.kind === "answering");
920
+ return setItems(completed, getItems(completed).filter((item) => item.kind !== "answering"));
951
921
  };
952
922
  var closeAssistantMessage = (message) => {
953
923
  if (message.role !== "assistant") return message;
954
- return syncAssistantActivity({
924
+ return completeItems({
955
925
  ...message,
956
926
  isStreaming: false,
957
- isComplete: true,
958
- _activityReasoningStreaming: false
959
- });
927
+ isComplete: true
928
+ }, () => true);
960
929
  };
961
930
 
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
- };
931
+ // src/contract.ts
932
+ var ContractViolation = class extends Error {
933
+ constructor(message) {
934
+ super(message);
935
+ this.name = "ContractViolation";
936
+ }
976
937
  };
977
- var messageAgentKey = (message) => {
978
- if (message.role !== "assistant") return null;
979
- return message.senderAgentId ?? message.senderName ?? null;
938
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
939
+ var expectRecord = (value, path) => {
940
+ if (!isRecord(value)) throw new ContractViolation(`${path} must be an object`);
941
+ return value;
942
+ };
943
+ var expectString = (value, path) => {
944
+ if (typeof value !== "string" || value.trim().length === 0) throw new ContractViolation(`${path} must be a non-empty string`);
945
+ return value;
946
+ };
947
+ var expectOptionalString = (value, path) => {
948
+ if (value === void 0 || value === null) return void 0;
949
+ return expectString(value, path);
950
+ };
951
+ var expectStringValue = (value, path) => {
952
+ if (typeof value !== "string") throw new ContractViolation(`${path} must be a string`);
953
+ return value;
954
+ };
955
+
956
+ // src/senders.ts
957
+ var clean = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
958
+ var expectSenderType = (value, path) => {
959
+ if (value === "user" || value === "agent" || value === "tool" || value === "system") return value;
960
+ throw new ContractViolation(`${path} must be user, agent, tool, or system`);
961
+ };
962
+ var defined = (value) => Object.fromEntries(
963
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
964
+ );
965
+ var findAgent = (agents, ...candidates) => {
966
+ const values = candidates.filter(Boolean).map((value) => value.toLowerCase());
967
+ if (!agents || values.length === 0) return void 0;
968
+ return agents.find(
969
+ (agent) => values.includes(agent.id.toLowerCase()) || values.includes(agent.name.toLowerCase())
970
+ );
980
971
  };
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;
972
+ var fromAgent = (agent, overrides = {}) => defined({
973
+ type: overrides.type ?? "agent",
974
+ id: agent.id,
975
+ name: agent.name,
976
+ agentId: agent.id,
977
+ avatarUrl: agent.avatarUrl,
978
+ color: agent.color,
979
+ ...overrides
980
+ });
981
+ var resolveUserSender = (user) => defined({
982
+ type: "user",
983
+ id: user.id,
984
+ externalId: user.id,
985
+ name: clean(user.name) ?? user.id,
986
+ avatarUrl: clean(user.avatarUrl)
987
+ });
988
+ var resolveAssistantFallbackSender = (options = {}) => ({
989
+ type: "agent",
990
+ id: "assistant",
991
+ name: clean(options.assistantName) ?? "Assistant",
992
+ agentId: "assistant"
993
+ });
994
+ var resolveAgentSender = (identity, options = {}, overrides = {}) => {
995
+ const id = expectString(identity.id, "agent.id");
996
+ const name = expectString(identity.name, "agent.name");
997
+ const agent = findAgent(options.agents, id, name);
984
998
  if (agent) {
985
- return normalizeAgentIdentity(agent);
999
+ return fromAgent(agent, defined({
1000
+ ...overrides,
1001
+ externalId: id && id !== agent.id ? id : void 0
1002
+ }));
986
1003
  }
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
1004
+ return defined({
1005
+ type: overrides.type ?? "agent",
1006
+ id,
1007
+ name,
1008
+ agentId: id,
1009
+ ...overrides
991
1010
  });
992
1011
  };
993
- var canAttachToStreamingAssistant = (message, incomingAgentKey) => {
994
- if (!message || message.role !== "assistant" || !message.isStreaming) {
995
- return false;
1012
+ var resolveHydratedMessageSender = (message, options = {}) => {
1013
+ const metadata = message.metadata ? expectRecord(message.metadata, "message.metadata") : {};
1014
+ const type = expectSenderType(message.senderType, "message.senderType");
1015
+ const storedId = expectOptionalString(message.senderId, "message.senderId");
1016
+ const participantId = expectOptionalString(metadata.senderParticipantId, "message.metadata.senderParticipantId") ?? expectOptionalString(message.senderUserId, "message.senderUserId");
1017
+ const externalId = expectString(metadata.senderExternalId, "message.metadata.senderExternalId");
1018
+ const displayName = expectString(metadata.senderDisplayName, "message.metadata.senderDisplayName");
1019
+ if (type === "agent" || type === "tool") {
1020
+ const agent = findAgent(options.agents, externalId, displayName, storedId);
1021
+ if (agent) {
1022
+ return fromAgent(agent, defined({
1023
+ type,
1024
+ participantId: participantId ?? storedId,
1025
+ externalId: externalId && externalId !== agent.id ? externalId : void 0
1026
+ }));
1027
+ }
1028
+ return defined({
1029
+ type,
1030
+ id: externalId,
1031
+ name: displayName,
1032
+ agentId: externalId,
1033
+ participantId: participantId ?? storedId,
1034
+ externalId
1035
+ });
996
1036
  }
997
- const currentAgentKey = messageAgentKey(message);
998
- return !incomingAgentKey || !currentAgentKey || currentAgentKey === incomingAgentKey;
1037
+ if (type === "user") {
1038
+ return defined({
1039
+ type: "user",
1040
+ id: externalId,
1041
+ externalId,
1042
+ name: displayName,
1043
+ avatarUrl: clean(options.user?.avatarUrl),
1044
+ participantId: participantId ?? storedId
1045
+ });
1046
+ }
1047
+ return defined({
1048
+ type: "system",
1049
+ id: externalId,
1050
+ name: displayName,
1051
+ participantId: participantId ?? storedId,
1052
+ externalId
1053
+ });
999
1054
  };
1000
- var THREAD_MESSAGES_PAGE_SIZE = 50;
1001
- var createEmptyMessagePageInfo = () => ({
1002
- hasMoreBefore: false,
1003
- oldestMessageId: null,
1004
- newestMessageId: null
1005
- });
1006
- var normalizeToolStatus = (status) => {
1055
+ var resolveLiveEventSender = (event, options = {}) => {
1056
+ const raw = expectRecord(event, "stream event");
1057
+ const payload = raw.payload === void 0 ? raw : expectRecord(raw.payload, "stream event.payload");
1058
+ const agent = payload.agent ?? raw.agent;
1059
+ if (isRecord(agent)) {
1060
+ return resolveAgentSender({
1061
+ id: expectString(agent.id, "stream event.payload.agent.id"),
1062
+ name: expectString(agent.name, "stream event.payload.agent.name")
1063
+ }, options);
1064
+ }
1065
+ const sender = payload.sender ?? raw.sender;
1066
+ if (!isRecord(sender)) {
1067
+ throw new ContractViolation("stream event sender contract requires payload.agent or payload.sender");
1068
+ }
1069
+ const type = expectSenderType(sender.type ?? payload.senderType, "stream event.payload.sender.type");
1070
+ if (type !== "user") {
1071
+ return resolveAgentSender({
1072
+ id: expectString(sender.id ?? sender.externalId, "stream event.payload.sender.id"),
1073
+ name: expectString(sender.name, "stream event.payload.sender.name")
1074
+ }, options, { type });
1075
+ }
1076
+ if (!options.user) {
1077
+ throw new ContractViolation("user stream sender requires current user context");
1078
+ }
1079
+ return resolveUserSender(options.user);
1080
+ };
1081
+
1082
+ // src/assetsService.ts
1083
+ var ContractViolation2 = class extends Error {
1084
+ name = "ContractViolation";
1085
+ };
1086
+ var expectRecord2 = (value, path) => {
1087
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
1088
+ throw new ContractViolation2(`${path} must be an object`);
1089
+ };
1090
+ var expectString2 = (value, path) => {
1091
+ if (typeof value === "string" && value.trim().length > 0) return value;
1092
+ throw new ContractViolation2(`${path} must be a non-empty string`);
1093
+ };
1094
+ var rawBaseValue2 = import.meta.env?.VITE_API_URL;
1095
+ var rawBase2 = typeof rawBaseValue2 === "string" && rawBaseValue2.length > 0 ? rawBaseValue2 : "/api";
1096
+ var normalizedBase2 = rawBase2.replace(/\/$/, "");
1097
+ var API_BASE2 = normalizedBase2.startsWith("http") || normalizedBase2.startsWith("/") ? normalizedBase2 : `/${normalizedBase2}`;
1098
+ var apiUrl2 = (path) => `${API_BASE2}${path}`;
1099
+ var extractAssetId = (refOrId) => refOrId.startsWith("asset://") ? refOrId.slice("asset://".length) : refOrId;
1100
+ async function getAssetDataUrl(refOrId) {
1101
+ const id = extractAssetId(refOrId);
1102
+ const res = await fetch(apiUrl2(`/v1/assets/${encodeURIComponent(id)}?format=dataUrl`), {
1103
+ method: "GET",
1104
+ headers: { Accept: "application/json" }
1105
+ });
1106
+ if (!res.ok) {
1107
+ const text = await res.text().catch(() => res.statusText);
1108
+ throw new Error(text || `Failed to fetch asset ${refOrId}`);
1109
+ }
1110
+ const body = await res.json();
1111
+ const envelope = expectRecord2(body, "asset response");
1112
+ const data = expectRecord2(envelope.data, "asset response.data");
1113
+ if (typeof data.error === "string" && data.error.length > 0) {
1114
+ throw new Error(data.error);
1115
+ }
1116
+ if (typeof data.dataUrl !== "string" || data.dataUrl.length === 0) {
1117
+ throw new ContractViolation2(`asset response.data.dataUrl is required for ${refOrId}`);
1118
+ }
1119
+ return {
1120
+ dataUrl: data.dataUrl,
1121
+ mime: typeof data.mime === "string" ? data.mime : void 0,
1122
+ assetId: expectString2(data.assetId, "asset response.data.assetId")
1123
+ };
1124
+ }
1125
+ async function resolveAssetsInMessages(messages) {
1126
+ const inFlightByRef = /* @__PURE__ */ new Map();
1127
+ const resolveAssetRef = (assetRef) => {
1128
+ if (!inFlightByRef.has(assetRef)) {
1129
+ inFlightByRef.set(assetRef, getAssetDataUrl(assetRef));
1130
+ }
1131
+ return inFlightByRef.get(assetRef);
1132
+ };
1133
+ return Promise.all(messages.map(async (msg) => {
1134
+ const meta = msg.metadata === null || msg.metadata === void 0 ? void 0 : expectRecord2(msg.metadata, "message.metadata");
1135
+ const attachments = meta?.attachments === void 0 ? void 0 : Array.isArray(meta.attachments) ? meta.attachments.map(
1136
+ (att, index) => expectRecord2(att, `message.metadata.attachments[${index}]`)
1137
+ ) : (() => {
1138
+ throw new ContractViolation2("message.metadata.attachments must be an array");
1139
+ })();
1140
+ if (!attachments || attachments.length === 0) {
1141
+ return msg;
1142
+ }
1143
+ const newAttachments = await Promise.all(attachments.map(async (att, index) => {
1144
+ const assetRef = typeof att.assetRef === "string" ? att.assetRef : void 0;
1145
+ if (!assetRef) return att;
1146
+ const { dataUrl, mime } = await resolveAssetRef(assetRef);
1147
+ const kind = expectString2(att.kind, `message.metadata.attachments[${index}].kind`);
1148
+ const mimeType = typeof att.mimeType === "string" ? att.mimeType : expectString2(mime, `asset ${assetRef}.mime`);
1149
+ return {
1150
+ ...att,
1151
+ kind,
1152
+ dataUrl,
1153
+ mimeType
1154
+ };
1155
+ }));
1156
+ const newMeta = { ...meta, attachments: newAttachments };
1157
+ return { ...msg, metadata: newMeta };
1158
+ }));
1159
+ }
1160
+
1161
+ // src/toolActivity.ts
1162
+ var fail = (message) => {
1163
+ throw new ContractViolation(message);
1164
+ };
1165
+ var expectToolStatus = (status, path) => {
1007
1166
  if (status === "pending") return "pending";
1008
1167
  if (status === "running" || status === "processing") return "running";
1009
1168
  if (status === "failed") return "failed";
1010
- return "completed";
1169
+ if (status === "completed") return "completed";
1170
+ return fail(`${path} must be pending, running, processing, completed, or failed`);
1011
1171
  };
1012
- var parseToolArguments = (value) => {
1013
- if (value && typeof value === "object" && !Array.isArray(value)) {
1014
- return value;
1015
- }
1172
+ var expectToolArguments = (value, path) => {
1173
+ if (isRecord(value)) return value;
1016
1174
  if (typeof value === "string") {
1017
1175
  try {
1018
1176
  const parsed = JSON.parse(value);
1019
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1020
- return parsed;
1021
- }
1177
+ return expectRecord(parsed, path);
1022
1178
  } catch {
1179
+ return fail(`${path} must be an object or JSON object string`);
1023
1180
  }
1024
1181
  }
1025
- return {};
1182
+ return fail(`${path} must be an object or JSON object string`);
1183
+ };
1184
+ var expectToolName = (tool, path) => {
1185
+ const name = typeof tool.name === "string" && tool.name.trim().length > 0 ? tool.name : tool.id;
1186
+ return expectString(name, path);
1026
1187
  };
1027
1188
  var matchesToolResultUpdate = (target, update) => {
1028
1189
  if (update.id && target.id) {
@@ -1030,92 +1191,84 @@ var matchesToolResultUpdate = (target, update) => {
1030
1191
  }
1031
1192
  return Boolean(update.name && target.name && update.name === target.name);
1032
1193
  };
1033
- var findMatchingToolCallIndex = (toolCalls, update) => toolCalls.findIndex((toolCall) => matchesToolResultUpdate(
1034
- { id: toolCall.id, name: toolCall.name },
1194
+ var findMatchingToolItem = (toolItems, update) => toolItems.find((item) => matchesToolResultUpdate(
1195
+ { id: item.id, name: item.toolName },
1035
1196
  update
1036
- ) && (toolCall.status === "pending" || toolCall.status === "running" || typeof toolCall.result === "undefined"));
1197
+ ) && (item.status === "active" || item.details?.result === void 0));
1037
1198
  var applyToolResultUpdateToMessages = (messages, update, assistantPatch) => {
1038
1199
  const nextMessages = [...messages];
1039
1200
  for (let i = nextMessages.length - 1; i >= 0; i--) {
1040
1201
  const message = nextMessages[i];
1041
- if (message.role !== "assistant" || !Array.isArray(message._activityToolCalls) || message._activityToolCalls.length === 0) {
1202
+ const toolItems = message.activity?.items.filter((item) => item.kind === "tool") ?? [];
1203
+ if (message.role !== "assistant" || toolItems.length === 0) {
1042
1204
  continue;
1043
1205
  }
1044
- const toolCallIndex = findMatchingToolCallIndex(message._activityToolCalls, update);
1045
- if (toolCallIndex === -1) continue;
1046
- nextMessages[i] = syncAssistantActivity({
1206
+ const toolItem = findMatchingToolItem(toolItems, update);
1207
+ if (!toolItem) continue;
1208
+ nextMessages[i] = {
1047
1209
  ...applyAssistantToolResult(message, {
1048
1210
  ...update.id ? { id: update.id } : {},
1049
- name: update.name ?? message._activityToolCalls[toolCallIndex].name,
1211
+ name: update.name ?? toolItem.toolName ?? toolItem.id,
1050
1212
  status: update.status,
1051
1213
  ...update.result !== void 0 ? { result: update.result } : {},
1052
1214
  endTime: update.endTime
1053
1215
  }),
1054
1216
  ...assistantPatch ?? {}
1055
- });
1217
+ };
1056
1218
  return { messages: nextMessages, matched: true };
1057
1219
  }
1058
1220
  return { messages, matched: false };
1059
1221
  };
1060
1222
  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;
1223
+ const payloadRecord = expectRecord(payload, "TOOL_CALL payload");
1224
+ const toolCall = expectRecord(payloadRecord.toolCall, "TOOL_CALL payload.toolCall");
1225
+ const tool = expectRecord(toolCall.tool, "TOOL_CALL payload.toolCall.tool");
1066
1226
  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 } : {}
1227
+ id: expectString(toolCall.id, "TOOL_CALL payload.toolCall.id"),
1228
+ name: expectToolName(tool, "TOOL_CALL payload.toolCall.tool.id"),
1229
+ arguments: expectToolArguments(toolCall.args, "TOOL_CALL payload.toolCall.args"),
1230
+ status: toolCall.status === void 0 ? "running" : expectToolStatus(toolCall.status, "TOOL_CALL payload.toolCall.status"),
1231
+ ...toolCall.output !== void 0 ? { result: toolCall.output } : {}
1072
1232
  };
1073
1233
  };
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;
1234
+ var extractLiveToolResultUpdate = (payload, now = () => Date.now()) => {
1235
+ const payloadRecord = expectRecord(payload, "TOOL_RESULT payload");
1236
+ const tool = expectRecord(payloadRecord.tool, "TOOL_RESULT payload.tool");
1237
+ const result = payloadRecord.projectedOutput !== void 0 ? payloadRecord.projectedOutput : payloadRecord.output;
1238
+ if (result === void 0) fail("TOOL_RESULT payload requires output or projectedOutput");
1077
1239
  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()
1240
+ id: expectString(payloadRecord.toolCallId, "TOOL_RESULT payload.toolCallId"),
1241
+ name: expectToolName(tool, "TOOL_RESULT payload.tool.id"),
1242
+ status: expectToolStatus(payloadRecord.status, "TOOL_RESULT payload.status"),
1243
+ result,
1244
+ endTime: now()
1083
1245
  };
1084
1246
  };
1085
1247
  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 : [];
1248
+ const metadata = msg.metadata === null || msg.metadata === void 0 ? void 0 : expectRecord(msg.metadata, "message.metadata");
1249
+ const topLevelToolCalls = readToolCallArray(msg.toolCalls, "message.toolCalls");
1250
+ const metadataToolCalls = readToolCallArray(metadata?.toolCalls, "message.metadata.toolCalls");
1089
1251
  const usedMetadataIndexes = /* @__PURE__ */ new Set();
1090
1252
  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
1253
  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;
1254
+ const id = expectString(toolCall.id, "message.toolCalls[].id");
1255
+ return metadataToolCalls.findIndex(
1256
+ (candidate, idx) => !usedMetadataIndexes.has(idx) && candidate.id === id
1257
+ );
1104
1258
  };
1105
1259
  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);
1260
+ const id = expectString(primary.id ?? secondary?.id, "toolCall.id");
1261
+ const tool = expectRecord(primary.tool ?? secondary?.tool, "toolCall.tool");
1262
+ const name = expectToolName(tool, "toolCall.tool.id");
1263
+ const argsRaw = primary.args ?? secondary?.args;
1264
+ const result = primary.output !== void 0 ? primary.output : secondary?.output !== void 0 ? secondary.output : primary.projectedOutput !== void 0 ? primary.projectedOutput : secondary?.projectedOutput;
1265
+ const rawStatus = primary.status ?? secondary?.status;
1113
1266
  return {
1114
- ...id ? { id } : {},
1267
+ id,
1115
1268
  name,
1116
- arguments: parseToolArguments(argsRaw),
1269
+ arguments: expectToolArguments(argsRaw, "toolCall.args"),
1117
1270
  ...result !== void 0 ? { result } : {},
1118
- status
1271
+ status: rawStatus === void 0 ? "running" : expectToolStatus(rawStatus, "toolCall.status")
1119
1272
  };
1120
1273
  };
1121
1274
  topLevelToolCalls.forEach((toolCall) => {
@@ -1130,20 +1283,24 @@ var extractToolCallsFromServerMessage = (msg) => {
1130
1283
  });
1131
1284
  return parsed;
1132
1285
  };
1133
- var extractToolResultUpdateFromMessage = (msg) => {
1286
+ var readToolCallArray = (value, path) => {
1287
+ if (value === null || value === void 0) return [];
1288
+ if (!Array.isArray(value)) fail(`${path} must be an array`);
1289
+ return value.map((toolCall, index) => expectRecord(toolCall, `${path}[${index}]`));
1290
+ };
1291
+ var extractToolResultUpdateFromMessage = (msg, now = () => Date.now()) => {
1134
1292
  if (msg.senderType !== "tool") return null;
1135
1293
  const toolCalls = extractToolCallsFromServerMessage(msg);
1136
- if (!Array.isArray(toolCalls) || toolCalls.length === 0) return null;
1294
+ if (toolCalls.length === 0) fail("tool message requires metadata.toolCalls");
1137
1295
  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;
1296
+ if (firstToolCall.result === void 0) fail("tool result message requires tool call output");
1297
+ expectStringValue(msg.createdAt, "tool result message.createdAt");
1141
1298
  return {
1142
- ...firstToolCall.id ? { id: firstToolCall.id } : {},
1143
- ...firstToolCall.name ? { name: firstToolCall.name } : {},
1144
- ...result !== void 0 ? { result } : {},
1299
+ id: firstToolCall.id,
1300
+ name: firstToolCall.name,
1301
+ result: firstToolCall.result,
1145
1302
  status: firstToolCall.status,
1146
- endTime: msg.createdAt ? new Date(msg.createdAt).getTime() : nowTs()
1303
+ endTime: new Date(msg.createdAt).getTime()
1147
1304
  };
1148
1305
  };
1149
1306
  var mergePersistedToolResults = (messages, updates) => {
@@ -1166,42 +1323,95 @@ var prependUniqueMessages = (olderMessages, currentMessages) => {
1166
1323
  }
1167
1324
  return combined;
1168
1325
  };
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 [];
1326
+ var messageAgentKey = (message) => {
1327
+ if (message.role !== "assistant") return null;
1328
+ if (message.sender?.type === "agent" || message.sender?.type === "tool") {
1329
+ return message.sender.agentId ?? message.sender.id;
1330
+ }
1331
+ return null;
1332
+ };
1333
+ var canAttachToStreamingAssistant = (message, incomingAgentKey) => {
1334
+ if (!message || message.role !== "assistant" || !message.isStreaming) {
1335
+ return false;
1336
+ }
1337
+ const currentAgentKey = messageAgentKey(message);
1338
+ return !incomingAgentKey || !currentAgentKey || currentAgentKey === incomingAgentKey;
1339
+ };
1340
+
1341
+ // src/messageContract.ts
1342
+ var isInternalMessageMetadata = (metadata) => metadata?.visibility === "internal";
1343
+ var defaultCreateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1344
+ var defaultNow = () => Date.now();
1345
+ var roleBySender = {
1346
+ user: "user",
1347
+ agent: "assistant",
1348
+ tool: "assistant",
1349
+ system: "system"
1350
+ };
1351
+ var expectNumber = (value, path) => {
1352
+ if (typeof value !== "number" || Number.isNaN(value)) throw new ContractViolation(`${path} must be a number`);
1353
+ return value;
1354
+ };
1355
+ var extractAttachments = (metadata) => {
1356
+ if (metadata?.attachments === void 0) return [];
1357
+ if (!Array.isArray(metadata.attachments)) {
1358
+ throw new ContractViolation("message.metadata.attachments must be an array");
1359
+ }
1360
+ return metadata.attachments.map((value, index) => {
1361
+ const path = `message.metadata.attachments[${index}]`;
1362
+ const att = expectRecord(value, path);
1363
+ const base = {
1364
+ kind: expectString(att.kind, `${path}.kind`),
1365
+ dataUrl: expectString(att.dataUrl, `${path}.dataUrl`),
1366
+ mimeType: expectString(att.mimeType, `${path}.mimeType`)
1367
+ };
1368
+ if (base.kind === "image") return base;
1369
+ if (base.kind === "audio") return {
1370
+ ...base,
1371
+ ...att.durationMs !== void 0 ? { durationMs: expectNumber(att.durationMs, `${path}.durationMs`) } : {}
1372
+ };
1373
+ if (base.kind === "video") return {
1374
+ ...base,
1375
+ ...att.durationMs !== void 0 ? { durationMs: expectNumber(att.durationMs, `${path}.durationMs`) } : {},
1376
+ ...att.poster !== void 0 ? { poster: expectString(att.poster, `${path}.poster`) } : {}
1377
+ };
1378
+ throw new ContractViolation(`${path}.kind must be image, audio, or video`);
1199
1379
  });
1200
- const role = msg.senderType === "agent" ? "assistant" : msg.senderType === "user" ? "user" : "assistant";
1380
+ };
1381
+ var assertRestMessageContract = (msg) => {
1382
+ expectString(msg.id, "message.id");
1383
+ expectString(msg.threadId, "message.threadId");
1384
+ if (!(msg.senderType in roleBySender)) throw new ContractViolation("message.senderType must be user, agent, tool, or system");
1385
+ expectStringValue(msg.content, "message.content");
1386
+ if (msg.metadata !== void 0 && msg.metadata !== null) expectRecord(msg.metadata, "message.metadata");
1387
+ if (msg.createdAt !== void 0) expectString(msg.createdAt, "message.createdAt");
1388
+ };
1389
+ var shouldRenderHydratedMessage = (msg) => {
1390
+ assertRestMessageContract(msg);
1391
+ const meta = msg.metadata ?? {};
1392
+ if (isInternalMessageMetadata(meta)) {
1393
+ return false;
1394
+ }
1395
+ const text = expectStringValue(msg.content, "message.content").trim();
1396
+ const hasText = text.length > 0;
1397
+ const hasToolCalls = extractToolCallsFromServerMessage(msg).length > 0;
1398
+ const hasAttachments = extractAttachments(meta).length > 0;
1399
+ if (msg.senderType === "tool") {
1400
+ return hasAttachments;
1401
+ }
1402
+ return hasText || hasToolCalls || hasAttachments;
1403
+ };
1404
+ var convertServerMessage = (msg, options = {}) => {
1405
+ assertRestMessageContract(msg);
1406
+ const timestamp = msg.createdAt ? new Date(msg.createdAt).getTime() : (options.now ?? defaultNow)();
1407
+ const metadata = msg.metadata ?? void 0;
1408
+ const attachments = extractAttachments(metadata);
1409
+ const messageContent = expectStringValue(msg.content, "message.content");
1410
+ const role = roleBySender[msg.senderType];
1201
1411
  const parsedToolCalls = extractToolCallsFromServerMessage(msg);
1202
1412
  const shouldRenderToolCalls = msg.senderType !== "tool";
1203
1413
  const mappedToolCalls = parsedToolCalls.map((toolCall) => ({
1204
- id: toolCall.id ?? generateId(),
1414
+ id: toolCall.id ?? (options.createId ?? defaultCreateId)(),
1205
1415
  name: toolCall.name,
1206
1416
  arguments: toolCall.arguments,
1207
1417
  status: toolCall.status,
@@ -1209,11 +1419,31 @@ var convertServerMessage = (msg) => {
1209
1419
  }));
1210
1420
  const hasToolCalls = shouldRenderToolCalls && mappedToolCalls.length > 0;
1211
1421
  const isToolSender = msg.senderType === "tool";
1212
- const content = isToolSender ? "" : (msg.content ?? "") || (hasToolCalls ? "" : "");
1422
+ const content = isToolSender ? "" : messageContent;
1213
1423
  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({
1424
+ const activityItems = [
1425
+ ...reasoning ? [{
1426
+ id: `${msg.id}:thinking`,
1427
+ kind: "thinking",
1428
+ status: "complete",
1429
+ completedAt: timestamp,
1430
+ details: { reasoning }
1431
+ }] : [],
1432
+ ...hasToolCalls ? mappedToolCalls.map((toolCall) => ({
1433
+ id: toolCall.id,
1434
+ kind: "tool",
1435
+ status: toolCall.status === "failed" ? "failed" : toolCall.status === "completed" ? "complete" : "active",
1436
+ toolName: toolCall.name,
1437
+ startedAt: toolCall.startTime,
1438
+ completedAt: toolCall.endTime,
1439
+ details: {
1440
+ toolCall,
1441
+ ...toolCall.result !== void 0 ? { result: toolCall.result } : {}
1442
+ }
1443
+ })) : []
1444
+ ];
1445
+ const sender = resolveHydratedMessageSender(msg, options.senderOptions);
1446
+ return {
1217
1447
  id: msg.id,
1218
1448
  role,
1219
1449
  content,
@@ -1222,14 +1452,49 @@ var convertServerMessage = (msg) => {
1222
1452
  isStreaming: false,
1223
1453
  isComplete: true,
1224
1454
  metadata,
1225
- _activityToolCalls: hasToolCalls ? mappedToolCalls : void 0,
1226
- ...reasoning ? { _activityReasoning: reasoning } : {},
1227
- ...senderAgentId ? { senderAgentId } : {},
1228
- ...senderName ? { senderName } : {}
1455
+ activity: activityItems.length > 0 ? { items: activityItems } : void 0,
1456
+ sender
1457
+ };
1458
+ };
1459
+ var prepareHydratedMessages = async (rawMessages, options = {}) => {
1460
+ rawMessages.forEach(assertRestMessageContract);
1461
+ const resolvedMessages = await resolveAssetsInMessages(rawMessages);
1462
+ resolvedMessages.forEach((msg) => {
1463
+ if (msg.senderType === "tool") {
1464
+ const metadata = msg.metadata ?? void 0;
1465
+ if (!metadata) {
1466
+ throw new ContractViolation("tool message requires metadata");
1467
+ }
1468
+ options.onToolOutput?.(metadata.output === void 0 ? metadata : { output: metadata.output });
1469
+ }
1229
1470
  });
1471
+ const now = options.now ?? defaultNow;
1472
+ const toolResultUpdates = resolvedMessages.map((msg) => extractToolResultUpdateFromMessage(msg, now)).filter((update) => update !== null);
1473
+ const viewMessages = resolvedMessages.filter(shouldRenderHydratedMessage).map((msg) => convertServerMessage(msg, options));
1474
+ return {
1475
+ viewMessages,
1476
+ toolResultUpdates
1477
+ };
1230
1478
  };
1479
+
1480
+ // src/useCopilotzChat.ts
1481
+ var nowTs = () => Date.now();
1482
+ var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1483
+ var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1484
+ var getEventPayload = (event) => event?.payload ?? event;
1485
+ var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
1486
+ var THREAD_MESSAGES_PAGE_SIZE = 50;
1487
+ var createEmptyMessagePageInfo = () => ({
1488
+ hasMoreBefore: false,
1489
+ oldestMessageId: null,
1490
+ newestMessageId: null
1491
+ });
1231
1492
  function useCopilotz({
1232
1493
  userId,
1494
+ userName,
1495
+ userAvatar,
1496
+ assistantName,
1497
+ agentOptions = [],
1233
1498
  initialContext,
1234
1499
  bootstrap,
1235
1500
  defaultThreadName,
@@ -1269,6 +1534,11 @@ function useCopilotz({
1269
1534
  const userContextSeedRef = useRef2(userContextSeed);
1270
1535
  const messagePageInfoRef = useRef2(messagePageInfo);
1271
1536
  const isLoadingOlderMessagesRef = useRef2(isLoadingOlderMessages);
1537
+ const senderOptionsRef = useRef2({
1538
+ agents: agentOptions,
1539
+ user: userId ? { id: userId, name: userName, avatarUrl: userAvatar } : null,
1540
+ assistantName
1541
+ });
1272
1542
  const persistedToolUpdatesRef = useRef2([]);
1273
1543
  const liveToolUpdatesRef = useRef2([]);
1274
1544
  threadsRef.current = threads;
@@ -1279,6 +1549,11 @@ function useCopilotz({
1279
1549
  userContextSeedRef.current = userContextSeed;
1280
1550
  messagePageInfoRef.current = messagePageInfo;
1281
1551
  isLoadingOlderMessagesRef.current = isLoadingOlderMessages;
1552
+ senderOptionsRef.current = {
1553
+ agents: agentOptions,
1554
+ user: userId ? { id: userId, name: userName, avatarUrl: userAvatar } : null,
1555
+ assistantName
1556
+ };
1282
1557
  preferredAgentRef.current = preferredAgentName ?? null;
1283
1558
  participantsRef.current = participants ?? null;
1284
1559
  targetAgentNameRef.current = targetAgentName ?? null;
@@ -1335,20 +1610,20 @@ function useCopilotz({
1335
1610
  }
1336
1611
  const senderType = getEventSenderType(payload);
1337
1612
  if (senderType !== "agent" || typeof payload.content !== "string") return;
1338
- const agentIdentity = resolveLiveAgentIdentity(event);
1339
- const incomingAgentKey = agentIdentity.senderAgentId ?? agentIdentity.senderName ?? null;
1613
+ const sender = resolveLiveEventSender(event, senderOptionsRef.current);
1614
+ const incomingAgentKey = sender.agentId ?? sender.id;
1340
1615
  setMessages((prev) => {
1341
1616
  const next = [...prev];
1342
1617
  for (let i = next.length - 1; i >= 0; i--) {
1343
1618
  const m = next[i];
1344
1619
  if (canAttachToStreamingAssistant(m, incomingAgentKey)) {
1345
- next[i] = syncAssistantActivity({
1620
+ next[i] = {
1346
1621
  ...m,
1347
1622
  content: payload.content,
1348
1623
  isStreaming: false,
1349
1624
  isComplete: true,
1350
- ...agentIdentity
1351
- });
1625
+ sender
1626
+ };
1352
1627
  return next;
1353
1628
  }
1354
1629
  }
@@ -1358,7 +1633,7 @@ function useCopilotz({
1358
1633
  }
1359
1634
  return [
1360
1635
  ...next,
1361
- syncAssistantActivity({
1636
+ {
1362
1637
  id: generateId(),
1363
1638
  role: "assistant",
1364
1639
  content: payload.content,
@@ -1366,8 +1641,8 @@ function useCopilotz({
1366
1641
  isStreaming: false,
1367
1642
  isComplete: true,
1368
1643
  metadata: liveMetadata,
1369
- ...agentIdentity
1370
- })
1644
+ sender
1645
+ }
1371
1646
  ];
1372
1647
  });
1373
1648
  }, []);
@@ -1424,33 +1699,12 @@ function useCopilotz({
1424
1699
  }
1425
1700
  }, [updateThreadsState, getRequestHeaders]);
1426
1701
  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
- }
1702
+ return prepareHydratedMessages(rawMessages, {
1703
+ senderOptions: senderOptionsRef.current,
1704
+ createId: generateId,
1705
+ now: nowTs,
1706
+ onToolOutput: processToolOutput
1434
1707
  });
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
1708
  }, [processToolOutput]);
1455
1709
  const loadThreadMessages = useCallback2(async (threadId) => {
1456
1710
  const requestId = messagesRequestRef.current + 1;
@@ -1658,14 +1912,14 @@ function useCopilotz({
1658
1912
  dataUrl,
1659
1913
  mimeType
1660
1914
  };
1661
- setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? syncAssistantActivity({
1915
+ setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? {
1662
1916
  ...msg,
1663
1917
  attachments: [...msg.attachments || [], mediaAttachment]
1664
- }) : msg));
1918
+ } : msg));
1665
1919
  }, []);
1666
1920
  const sendCopilotzMessage = useCallback2(async (params) => {
1667
- let currentAssistantId = generateId();
1668
- let currentAssistantIdentity = {};
1921
+ let currentAssistantId = params.assistantMessageId ?? generateId();
1922
+ let currentAssistantSender = params.assistantSender;
1669
1923
  params.onBeforeStart?.(currentAssistantId);
1670
1924
  let hasStreamProgress = false;
1671
1925
  const updateStreamingMessage = (partial, opts) => {
@@ -1673,28 +1927,26 @@ function useCopilotz({
1673
1927
  hasStreamProgress = true;
1674
1928
  }
1675
1929
  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
- };
1930
+ const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
1931
+ if (nextSender) {
1932
+ currentAssistantSender = nextSender;
1682
1933
  }
1683
- const agentIdentity = currentAssistantIdentity;
1684
- const nextAgentKey = agentIdentity.senderAgentId ?? agentIdentity.senderName ?? null;
1934
+ const nextAgentKey = currentAssistantSender?.agentId ?? currentAssistantSender?.id ?? null;
1685
1935
  const applyUpdate = (msg) => {
1686
- return updateAssistantMessageToken(msg, {
1687
- partial,
1688
- isReasoning,
1689
- agentIdentity
1690
- });
1936
+ return {
1937
+ ...updateAssistantMessageToken(msg, {
1938
+ partial,
1939
+ isReasoning
1940
+ }),
1941
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1942
+ };
1691
1943
  };
1692
1944
  setMessages((prev) => {
1693
1945
  const idx = prev.findIndex((m) => m.id === currentAssistantId);
1694
1946
  if (idx >= 0 && canAttachToStreamingAssistant(prev[idx], nextAgentKey)) {
1695
1947
  const msg = prev[idx];
1696
1948
  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) {
1949
+ if (msg.content === next.content && msg.activity === next.activity && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
1698
1950
  return prev;
1699
1951
  }
1700
1952
  const updated = [...prev];
@@ -1705,7 +1957,7 @@ function useCopilotz({
1705
1957
  if (canAttachToStreamingAssistant(last, nextAgentKey)) {
1706
1958
  currentAssistantId = last.id;
1707
1959
  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) {
1960
+ if (last.content === next.content && last.activity === next.activity && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
1709
1961
  return prev;
1710
1962
  }
1711
1963
  const updated = [...prev];
@@ -1723,7 +1975,7 @@ function useCopilotz({
1723
1975
  timestamp: nowTs(),
1724
1976
  isStreaming: true,
1725
1977
  isComplete: false,
1726
- ...currentAssistantIdentity
1978
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1727
1979
  };
1728
1980
  return [...prev, applyUpdate(base)];
1729
1981
  }
@@ -1770,7 +2022,7 @@ function useCopilotz({
1770
2022
  if (fallbackIdx < 0) return prev;
1771
2023
  const message = prev[fallbackIdx];
1772
2024
  const nextMessage = finalizeAssistantMessage(message, finalAnswer);
1773
- if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message._activityReasoningStreaming === nextMessage._activityReasoningStreaming) {
2025
+ if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity) {
1774
2026
  return prev;
1775
2027
  }
1776
2028
  const updated = [...prev];
@@ -1785,7 +2037,6 @@ function useCopilotz({
1785
2037
  const payload = event?.payload ?? event;
1786
2038
  if (type === "TOOL_CALL") {
1787
2039
  const parsedToolCall = extractLiveToolCall(payload);
1788
- if (!parsedToolCall) return null;
1789
2040
  return {
1790
2041
  id: generateId(),
1791
2042
  threadId: curThreadId ?? "",
@@ -1871,14 +2122,9 @@ function useCopilotz({
1871
2122
  const parsedToolCall = extractLiveToolCall(
1872
2123
  payload ?? {}
1873
2124
  );
1874
- if (!parsedToolCall) return;
1875
- const eventAgentIdentity = resolveLiveAgentIdentity(event);
1876
- if (eventAgentIdentity.senderAgentId || eventAgentIdentity.senderName) {
1877
- currentAssistantIdentity = {
1878
- ...currentAssistantIdentity,
1879
- ...eventAgentIdentity
1880
- };
1881
- }
2125
+ const eventSender = resolveLiveEventSender(event, senderOptionsRef.current);
2126
+ currentAssistantSender = eventSender;
2127
+ const eventAgentKey = currentAssistantSender.agentId ?? currentAssistantSender.id;
1882
2128
  const callId = parsedToolCall.id ?? generateId();
1883
2129
  const toolName = parsedToolCall.name;
1884
2130
  const bufferedUpdates = liveToolUpdatesRef.current;
@@ -1892,6 +2138,10 @@ function useCopilotz({
1892
2138
  const endTime = bufferedUpdate?.endTime;
1893
2139
  setMessages(
1894
2140
  (prev) => (() => {
2141
+ const canHostActivity = (message) => {
2142
+ if (!message) return false;
2143
+ return message.role === "assistant" && message.isStreaming && message.content.trim().length === 0 && !message.attachments?.length;
2144
+ };
1895
2145
  const appendToolCall = (msg) => ({
1896
2146
  ...appendAssistantToolCall(msg, {
1897
2147
  id: callId,
@@ -1903,21 +2153,21 @@ function useCopilotz({
1903
2153
  ...endTime !== void 0 ? { endTime } : {}
1904
2154
  })
1905
2155
  });
1906
- const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming);
2156
+ const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming && canHostActivity(message));
1907
2157
  if (currentIdx >= 0) {
1908
2158
  const next = [...prev];
1909
2159
  next[currentIdx] = appendToolCall({
1910
2160
  ...next[currentIdx],
1911
2161
  isStreaming: true,
1912
2162
  isComplete: false,
1913
- ...currentAssistantIdentity
2163
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1914
2164
  });
1915
2165
  return next;
1916
2166
  }
1917
2167
  const last = prev[prev.length - 1];
1918
- if (canAttachToStreamingAssistant(
2168
+ if (canHostActivity(last) && canAttachToStreamingAssistant(
1919
2169
  last,
1920
- currentAssistantIdentity.senderAgentId ?? currentAssistantIdentity.senderName ?? null
2170
+ eventAgentKey
1921
2171
  )) {
1922
2172
  currentAssistantId = last.id;
1923
2173
  const next = [...prev];
@@ -1925,7 +2175,7 @@ function useCopilotz({
1925
2175
  ...last,
1926
2176
  isStreaming: true,
1927
2177
  isComplete: false,
1928
- ...currentAssistantIdentity
2178
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1929
2179
  });
1930
2180
  return next;
1931
2181
  }
@@ -1940,7 +2190,7 @@ function useCopilotz({
1940
2190
  timestamp: nowTs(),
1941
2191
  isStreaming: true,
1942
2192
  isComplete: false,
1943
- ...currentAssistantIdentity
2193
+ ...currentAssistantSender ? { sender: currentAssistantSender } : {}
1944
2194
  })
1945
2195
  ];
1946
2196
  })()
@@ -1950,7 +2200,11 @@ function useCopilotz({
1950
2200
  }
1951
2201
  const sm = await toServerMessageFromEvent(event);
1952
2202
  if (sm) {
1953
- const viewMsg = convertServerMessage(sm);
2203
+ const viewMsg = convertServerMessage(sm, {
2204
+ senderOptions: senderOptionsRef.current,
2205
+ createId: generateId,
2206
+ now: nowTs
2207
+ });
1954
2208
  finalizeCurrentAssistantBubble();
1955
2209
  setMessages((prev) => [...prev, viewMsg]);
1956
2210
  return;
@@ -2008,8 +2262,19 @@ function useCopilotz({
2008
2262
  content,
2009
2263
  timestamp,
2010
2264
  attachments: attachments.length > 0 ? attachments : void 0,
2011
- isComplete: true
2265
+ isComplete: true,
2266
+ sender: resolveUserSender({
2267
+ id: userId,
2268
+ name: userContextSeedRef.current?.profile?.full_name ?? userId
2269
+ })
2012
2270
  };
2271
+ const assistantSender = targetAgentNameRef.current ? resolveAgentSender(
2272
+ { id: targetAgentNameRef.current, name: targetAgentNameRef.current },
2273
+ senderOptionsRef.current
2274
+ ) : preferredAgentRef.current ? resolveAgentSender(
2275
+ { id: preferredAgentRef.current, name: preferredAgentRef.current },
2276
+ senderOptionsRef.current
2277
+ ) : resolveAssistantFallbackSender(senderOptionsRef.current);
2013
2278
  const assistantPlaceholder = {
2014
2279
  id: generateId(),
2015
2280
  role: "assistant",
@@ -2017,9 +2282,9 @@ function useCopilotz({
2017
2282
  timestamp: timestamp + 1,
2018
2283
  isStreaming: true,
2019
2284
  isComplete: false,
2020
- ...targetAgentNameRef.current ? { senderName: targetAgentNameRef.current } : {}
2285
+ sender: assistantSender
2021
2286
  };
2022
- setMessages((prev) => [...prev, userMessage, syncAssistantActivity(assistantPlaceholder)]);
2287
+ setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);
2023
2288
  setSpecialState(null);
2024
2289
  if (!threadsRef.current.some((t) => t.id === conversationKey)) {
2025
2290
  const newThread = {
@@ -2043,6 +2308,8 @@ function useCopilotz({
2043
2308
  // userName can be anything, but let's try to find it in context or just fallback
2044
2309
  userName: userContextSeedRef.current?.profile?.full_name ?? userId,
2045
2310
  agentName: preferredAgentRef.current,
2311
+ assistantMessageId: assistantPlaceholder.id,
2312
+ assistantSender,
2046
2313
  // Include pending title for new threads
2047
2314
  threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
2048
2315
  });
@@ -2066,24 +2333,26 @@ function useCopilotz({
2066
2333
  const message = finalized[i];
2067
2334
  if (message.role !== "assistant") continue;
2068
2335
  const updated = [...finalized];
2069
- updated[i] = syncAssistantActivity({
2336
+ updated[i] = {
2070
2337
  ...message,
2071
2338
  content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2072
2339
  isStreaming: false,
2073
- isComplete: true
2074
- });
2340
+ isComplete: true,
2341
+ sender: message.sender ?? resolveAssistantFallbackSender(senderOptionsRef.current)
2342
+ };
2075
2343
  return updated;
2076
2344
  }
2077
2345
  return [
2078
2346
  ...finalized,
2079
- syncAssistantActivity({
2347
+ {
2080
2348
  id: generateId(),
2081
2349
  role: "assistant",
2082
2350
  content: "Desculpe, ocorreu um erro ao gerar a resposta. Por favor, tente novamente.",
2083
2351
  timestamp: nowTs(),
2084
2352
  isStreaming: false,
2085
- isComplete: true
2086
- })
2353
+ isComplete: true,
2354
+ sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2355
+ }
2087
2356
  ];
2088
2357
  });
2089
2358
  }
@@ -2122,14 +2391,15 @@ function useCopilotz({
2122
2391
  return;
2123
2392
  }
2124
2393
  setMessages([
2125
- syncAssistantActivity({
2394
+ {
2126
2395
  id: generateId(),
2127
2396
  role: "assistant",
2128
2397
  content: "N\xE3o foi poss\xEDvel iniciar a conversa. Tente novamente mais tarde.",
2129
2398
  timestamp: nowTs(),
2130
2399
  isStreaming: false,
2131
- isComplete: true
2132
- })
2400
+ isComplete: true,
2401
+ sender: resolveAssistantFallbackSender(senderOptionsRef.current)
2402
+ }
2133
2403
  ]);
2134
2404
  }
2135
2405
  }, [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName, getSpecialStateFromError]);
@@ -2275,6 +2545,10 @@ var CopilotzChat = ({
2275
2545
  loadOlderMessages
2276
2546
  } = useCopilotz({
2277
2547
  userId,
2548
+ userName,
2549
+ userAvatar,
2550
+ assistantName: userConfig?.branding?.title,
2551
+ agentOptions,
2278
2552
  initialContext,
2279
2553
  bootstrap,
2280
2554
  defaultThreadName: userConfig?.labels?.defaultThreadName,