@copilotz/chat-adapter 0.9.43 → 0.9.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -3
- package/dist/index.js +518 -343
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -101,6 +101,38 @@ var User = createLucideIcon("user", __iconNode);
|
|
|
101
101
|
// src/useCopilotzChat.ts
|
|
102
102
|
import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
103
103
|
|
|
104
|
+
// src/streamEvents.ts
|
|
105
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
+
var hasValues = (value) => Array.isArray(value) && value.length > 0;
|
|
107
|
+
var getStreamEventPayload = (event) => {
|
|
108
|
+
if (!isRecord(event)) return event;
|
|
109
|
+
return "payload" in event ? event.payload : event;
|
|
110
|
+
};
|
|
111
|
+
var getLlmAttemptId = (event) => {
|
|
112
|
+
if (!isRecord(event)) return null;
|
|
113
|
+
const metadata = isRecord(event.metadata) ? event.metadata : {};
|
|
114
|
+
if (typeof metadata.llmAttemptId === "string" && metadata.llmAttemptId.trim().length > 0) {
|
|
115
|
+
return metadata.llmAttemptId.trim();
|
|
116
|
+
}
|
|
117
|
+
if (event.subjectType === "llm_attempt" && typeof event.subjectId === "string" && event.subjectId.trim().length > 0) {
|
|
118
|
+
return event.subjectId.trim();
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
};
|
|
122
|
+
var isTerminalEmptyLlmResultEvent = (event) => {
|
|
123
|
+
if (!isRecord(event) || event.type !== "LLM_RESULT") return false;
|
|
124
|
+
const payload = getStreamEventPayload(event);
|
|
125
|
+
if (!isRecord(payload)) return false;
|
|
126
|
+
if (payload.answer !== "") return false;
|
|
127
|
+
if (payload.finishReason === "tool_calls") return false;
|
|
128
|
+
if (hasValues(payload.toolCalls)) return false;
|
|
129
|
+
const metadata = isRecord(event.metadata) ? event.metadata : {};
|
|
130
|
+
const routing = isRecord(metadata.routing) ? metadata.routing : {};
|
|
131
|
+
if ((routing.action === "ask" || routing.action === "handoff") && typeof routing.targetId === "string" && routing.targetId.trim().length > 0) return false;
|
|
132
|
+
if (hasValues(metadata.targetQueue)) return false;
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
|
|
104
136
|
// src/copilotzService.ts
|
|
105
137
|
var rawBaseValue = import.meta.env?.VITE_API_URL;
|
|
106
138
|
var rawBase = typeof rawBaseValue === "string" && rawBaseValue.length > 0 ? rawBaseValue : "/api";
|
|
@@ -428,6 +460,11 @@ async function runCopilotzStream(options) {
|
|
|
428
460
|
let lastCompletedText = "";
|
|
429
461
|
let lastTokenWasReasoning = false;
|
|
430
462
|
let hadNonReasoningContent = false;
|
|
463
|
+
let activeLlmAttemptId = null;
|
|
464
|
+
let activePhaseKind = null;
|
|
465
|
+
let activePhaseId = null;
|
|
466
|
+
let phaseOrdinal = -1;
|
|
467
|
+
let fallbackAttemptOrdinal = 0;
|
|
431
468
|
const collectedMessages = [];
|
|
432
469
|
let collectedMedia = null;
|
|
433
470
|
const resetTokenAggregation = () => {
|
|
@@ -436,6 +473,30 @@ async function runCopilotzStream(options) {
|
|
|
436
473
|
lastTokenWasReasoning = false;
|
|
437
474
|
hadNonReasoningContent = false;
|
|
438
475
|
};
|
|
476
|
+
const startAttempt = (event) => {
|
|
477
|
+
activeLlmAttemptId = getLlmAttemptId(event) ?? `stream-attempt:${fallbackAttemptOrdinal++}`;
|
|
478
|
+
activePhaseKind = null;
|
|
479
|
+
activePhaseId = null;
|
|
480
|
+
phaseOrdinal = -1;
|
|
481
|
+
resetTokenAggregation();
|
|
482
|
+
};
|
|
483
|
+
const getTokenContext = (isReasoning) => {
|
|
484
|
+
if (!activeLlmAttemptId) {
|
|
485
|
+
startAttempt(null);
|
|
486
|
+
}
|
|
487
|
+
const kind = isReasoning ? "reasoning" : "answer";
|
|
488
|
+
if (activePhaseKind !== kind || !activePhaseId) {
|
|
489
|
+
phaseOrdinal += 1;
|
|
490
|
+
activePhaseKind = kind;
|
|
491
|
+
activePhaseId = `${activeLlmAttemptId}:${kind}:${phaseOrdinal}`;
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
isReasoning,
|
|
495
|
+
llmAttemptId: activeLlmAttemptId,
|
|
496
|
+
phaseId: activePhaseId,
|
|
497
|
+
phaseOrdinal
|
|
498
|
+
};
|
|
499
|
+
};
|
|
439
500
|
const processEvent = async (eventChunk) => {
|
|
440
501
|
if (!eventChunk.trim()) return;
|
|
441
502
|
const lines = eventChunk.split("\n");
|
|
@@ -461,10 +522,16 @@ async function runCopilotzStream(options) {
|
|
|
461
522
|
return;
|
|
462
523
|
}
|
|
463
524
|
switch (eventType) {
|
|
525
|
+
case "LLM_CALL": {
|
|
526
|
+
startAttempt(payload2);
|
|
527
|
+
await onMessageEvent?.(payload2);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
464
530
|
case "TOKEN": {
|
|
465
531
|
const inner = payload2?.payload ?? payload2;
|
|
466
532
|
const chunk = typeof inner?.token === "string" ? inner.token : "";
|
|
467
|
-
const isReasoning =
|
|
533
|
+
const isReasoning = typeof inner?.isReasoning === "boolean" ? inner.isReasoning : activePhaseKind === "reasoning" || lastTokenWasReasoning;
|
|
534
|
+
const tokenContext = getTokenContext(isReasoning);
|
|
468
535
|
if (isReasoning && !lastTokenWasReasoning && hadNonReasoningContent) {
|
|
469
536
|
aggregatedReasoning = "";
|
|
470
537
|
aggregatedText = "";
|
|
@@ -482,12 +549,14 @@ async function runCopilotzStream(options) {
|
|
|
482
549
|
const isComplete = Boolean(inner?.isComplete);
|
|
483
550
|
if (chunk || isComplete) {
|
|
484
551
|
const tokenText = isReasoning ? aggregatedReasoning : aggregatedText;
|
|
485
|
-
onToken?.(tokenText, isComplete, payload2,
|
|
552
|
+
onToken?.(tokenText, isComplete, payload2, tokenContext);
|
|
486
553
|
if (isComplete) {
|
|
487
554
|
if (!isReasoning && tokenText) {
|
|
488
555
|
lastCompletedText = tokenText;
|
|
489
556
|
}
|
|
490
557
|
resetTokenAggregation();
|
|
558
|
+
activePhaseKind = null;
|
|
559
|
+
activePhaseId = null;
|
|
491
560
|
}
|
|
492
561
|
}
|
|
493
562
|
break;
|
|
@@ -833,8 +902,6 @@ function useUrlState(config = {}) {
|
|
|
833
902
|
}
|
|
834
903
|
|
|
835
904
|
// src/activity.ts
|
|
836
|
-
var thinkingId = "thinking";
|
|
837
|
-
var answeringId = "answering";
|
|
838
905
|
var getItems = (message) => Array.isArray(message.activity?.items) ? message.activity.items : [];
|
|
839
906
|
var setItems = (message, items) => ({
|
|
840
907
|
...message,
|
|
@@ -860,7 +927,8 @@ var upsertItem = (message, item) => {
|
|
|
860
927
|
};
|
|
861
928
|
return setItems(message, next);
|
|
862
929
|
};
|
|
863
|
-
var completeItems = (message, shouldComplete) => setItems(message, getItems(message).map((item) => item.status === "active" && shouldComplete(item) ? { ...item, status: "complete", completedAt
|
|
930
|
+
var completeItems = (message, shouldComplete, completedAt = Date.now()) => setItems(message, getItems(message).map((item) => item.status === "active" && shouldComplete(item) ? { ...item, status: "complete", completedAt } : item));
|
|
931
|
+
var removeItems = (message, shouldRemove) => setItems(message, getItems(message).filter((item) => !shouldRemove(item)));
|
|
864
932
|
var hasVisibleAssistantOutput = (message) => {
|
|
865
933
|
if (message.role !== "assistant") return false;
|
|
866
934
|
if (typeof message.content === "string" && message.content.trim().length > 0) return true;
|
|
@@ -875,38 +943,40 @@ var toPublicChatMessage = (message) => {
|
|
|
875
943
|
var updateAssistantMessageToken = (message, params) => {
|
|
876
944
|
if (message.role !== "assistant") return message;
|
|
877
945
|
if (params.isReasoning) {
|
|
878
|
-
|
|
946
|
+
const activityId2 = params.activityId ?? `${message.id}:thinking`;
|
|
947
|
+
return upsertItem(completeItems(removeItems({
|
|
879
948
|
...message,
|
|
880
949
|
isStreaming: true,
|
|
881
950
|
isComplete: false
|
|
882
|
-
}, {
|
|
883
|
-
id:
|
|
951
|
+
}, (item) => item.kind === "answering"), (item) => item.id !== activityId2 && (item.kind === "thinking" || item.kind === "answering"), params.at), {
|
|
952
|
+
id: activityId2,
|
|
884
953
|
kind: "thinking",
|
|
885
954
|
status: "active",
|
|
886
|
-
startedAt: getItems(message).find((item) => item.id ===
|
|
955
|
+
startedAt: getItems(message).find((item) => item.id === activityId2)?.startedAt ?? params.at ?? Date.now(),
|
|
887
956
|
details: { reasoning: params.partial }
|
|
888
957
|
});
|
|
889
958
|
}
|
|
890
|
-
|
|
959
|
+
const activityId = params.activityId ?? `${message.id}:answering`;
|
|
960
|
+
return upsertItem(completeItems(removeItems({
|
|
891
961
|
...message,
|
|
892
962
|
content: params.partial,
|
|
893
963
|
isStreaming: true,
|
|
894
964
|
isComplete: false
|
|
895
|
-
}, (item) => item.kind === "thinking" || item.kind === "
|
|
896
|
-
id:
|
|
965
|
+
}, (item) => item.kind === "answering" && item.id !== activityId), (item) => item.kind === "thinking" || item.kind === "answering", params.at), {
|
|
966
|
+
id: activityId,
|
|
897
967
|
kind: "answering",
|
|
898
968
|
status: "active",
|
|
899
|
-
startedAt: getItems(message).find((item) => item.id ===
|
|
969
|
+
startedAt: getItems(message).find((item) => item.id === activityId)?.startedAt ?? params.at ?? Date.now()
|
|
900
970
|
});
|
|
901
971
|
};
|
|
902
972
|
var appendAssistantToolCall = (message, toolCall) => {
|
|
903
973
|
if (message.role !== "assistant") return message;
|
|
904
974
|
const status = toolStatusToActivityStatus(toolCall.status);
|
|
905
|
-
return upsertItem({
|
|
975
|
+
return upsertItem(completeItems(removeItems({
|
|
906
976
|
...message,
|
|
907
977
|
isStreaming: true,
|
|
908
978
|
isComplete: false
|
|
909
|
-
}, {
|
|
979
|
+
}, (item) => item.kind === "answering"), (item) => item.kind === "thinking" || item.kind === "answering"), {
|
|
910
980
|
id: toolCall.id,
|
|
911
981
|
kind: "tool",
|
|
912
982
|
status,
|
|
@@ -955,23 +1025,23 @@ var applyAssistantToolResult = (message, update) => {
|
|
|
955
1025
|
};
|
|
956
1026
|
return setItems(message, next);
|
|
957
1027
|
};
|
|
958
|
-
var finalizeAssistantMessage = (message, finalAnswer) => {
|
|
1028
|
+
var finalizeAssistantMessage = (message, finalAnswer, completedAt = Date.now()) => {
|
|
959
1029
|
if (message.role !== "assistant") return message;
|
|
960
1030
|
const completed = completeItems({
|
|
961
1031
|
...message,
|
|
962
1032
|
...typeof finalAnswer === "string" && finalAnswer.length > 0 ? { content: finalAnswer } : {},
|
|
963
1033
|
isStreaming: false,
|
|
964
1034
|
isComplete: true
|
|
965
|
-
}, (item) => item.kind === "thinking" || item.kind === "answering");
|
|
1035
|
+
}, (item) => item.kind === "thinking" || item.kind === "answering", completedAt);
|
|
966
1036
|
return setItems(completed, getItems(completed).filter((item) => item.kind !== "answering"));
|
|
967
1037
|
};
|
|
968
|
-
var closeAssistantMessage = (message) => {
|
|
1038
|
+
var closeAssistantMessage = (message, completedAt = Date.now()) => {
|
|
969
1039
|
if (message.role !== "assistant") return message;
|
|
970
1040
|
return completeItems({
|
|
971
1041
|
...message,
|
|
972
1042
|
isStreaming: false,
|
|
973
1043
|
isComplete: true
|
|
974
|
-
}, () => true);
|
|
1044
|
+
}, () => true, completedAt);
|
|
975
1045
|
};
|
|
976
1046
|
|
|
977
1047
|
// src/contract.ts
|
|
@@ -981,9 +1051,9 @@ var ContractViolation = class extends Error {
|
|
|
981
1051
|
this.name = "ContractViolation";
|
|
982
1052
|
}
|
|
983
1053
|
};
|
|
984
|
-
var
|
|
1054
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
985
1055
|
var expectRecord = (value, path) => {
|
|
986
|
-
if (!
|
|
1056
|
+
if (!isRecord2(value)) throw new ContractViolation(`${path} must be an object`);
|
|
987
1057
|
return value;
|
|
988
1058
|
};
|
|
989
1059
|
var expectString = (value, path) => {
|
|
@@ -1104,14 +1174,14 @@ var resolveLiveEventSender = (event, options = {}) => {
|
|
|
1104
1174
|
const raw = expectRecord(event, "stream event");
|
|
1105
1175
|
const payload = raw.payload === void 0 ? raw : expectRecord(raw.payload, "stream event.payload");
|
|
1106
1176
|
const agent = payload.agent ?? raw.agent;
|
|
1107
|
-
if (
|
|
1177
|
+
if (isRecord2(agent)) {
|
|
1108
1178
|
return resolveAgentSender({
|
|
1109
1179
|
id: expectString(agent.id, "stream event.payload.agent.id"),
|
|
1110
1180
|
name: expectString(agent.name, "stream event.payload.agent.name")
|
|
1111
1181
|
}, options);
|
|
1112
1182
|
}
|
|
1113
1183
|
const sender = payload.sender ?? raw.sender;
|
|
1114
|
-
if (!
|
|
1184
|
+
if (!isRecord2(sender)) {
|
|
1115
1185
|
throw new ContractViolation("stream event sender contract requires payload.agent or payload.sender");
|
|
1116
1186
|
}
|
|
1117
1187
|
const type = expectSenderType(sender.type ?? payload.senderType, "stream event.payload.sender.type");
|
|
@@ -1241,6 +1311,98 @@ async function resolveAssetsInMessages(messages, getRequestHeaders) {
|
|
|
1241
1311
|
}));
|
|
1242
1312
|
}
|
|
1243
1313
|
|
|
1314
|
+
// src/messageReconciliation.ts
|
|
1315
|
+
var CLIENT_MESSAGE_ID_METADATA_KEY = "clientMessageId";
|
|
1316
|
+
var LLM_ATTEMPT_ID_METADATA_KEY = "llmAttemptId";
|
|
1317
|
+
var stringifyForCompare = (value) => {
|
|
1318
|
+
if (value === void 0) return "";
|
|
1319
|
+
try {
|
|
1320
|
+
return JSON.stringify(value) ?? "";
|
|
1321
|
+
} catch {
|
|
1322
|
+
return String(value);
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
var getMessageSignature = (message) => JSON.stringify({
|
|
1326
|
+
id: message.id,
|
|
1327
|
+
role: message.role,
|
|
1328
|
+
content: message.content ?? "",
|
|
1329
|
+
isStreaming: message.isStreaming === true,
|
|
1330
|
+
isComplete: message.isComplete === true,
|
|
1331
|
+
attachments: stringifyForCompare(message.attachments),
|
|
1332
|
+
activity: stringifyForCompare(message.activity),
|
|
1333
|
+
metadata: stringifyForCompare(message.metadata),
|
|
1334
|
+
sender: message.sender ? {
|
|
1335
|
+
id: message.sender.id,
|
|
1336
|
+
type: message.sender.type,
|
|
1337
|
+
name: message.sender.name,
|
|
1338
|
+
agentId: message.sender.agentId
|
|
1339
|
+
} : null
|
|
1340
|
+
});
|
|
1341
|
+
var getMetadataString = (message, key) => {
|
|
1342
|
+
const value = message.metadata?.[key];
|
|
1343
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1344
|
+
};
|
|
1345
|
+
var getCorrelationKeys = (message) => {
|
|
1346
|
+
const clientMessageId = getMetadataString(
|
|
1347
|
+
message,
|
|
1348
|
+
CLIENT_MESSAGE_ID_METADATA_KEY
|
|
1349
|
+
);
|
|
1350
|
+
const llmAttemptId = getMetadataString(
|
|
1351
|
+
message,
|
|
1352
|
+
LLM_ATTEMPT_ID_METADATA_KEY
|
|
1353
|
+
);
|
|
1354
|
+
return [
|
|
1355
|
+
...clientMessageId ? [`client-message:${message.role}:${clientMessageId}`] : [],
|
|
1356
|
+
...llmAttemptId ? [`llm-attempt:${message.role}:${llmAttemptId}`] : []
|
|
1357
|
+
];
|
|
1358
|
+
};
|
|
1359
|
+
var indexUniqueFreshCorrelations = (freshMessages) => {
|
|
1360
|
+
const messagesByCorrelation = /* @__PURE__ */ new Map();
|
|
1361
|
+
for (const message of freshMessages) {
|
|
1362
|
+
for (const key of getCorrelationKeys(message)) {
|
|
1363
|
+
messagesByCorrelation.set(
|
|
1364
|
+
key,
|
|
1365
|
+
messagesByCorrelation.has(key) ? null : message
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return messagesByCorrelation;
|
|
1370
|
+
};
|
|
1371
|
+
var reconcileThreadMessages = (currentMessages, freshMessages) => {
|
|
1372
|
+
if (freshMessages.length === 0) {
|
|
1373
|
+
return { messages: currentMessages, changed: false };
|
|
1374
|
+
}
|
|
1375
|
+
if (currentMessages.length === 0) {
|
|
1376
|
+
return { messages: freshMessages, changed: true };
|
|
1377
|
+
}
|
|
1378
|
+
const freshById = new Map(freshMessages.map((message) => [message.id, message]));
|
|
1379
|
+
const freshByCorrelation = indexUniqueFreshCorrelations(freshMessages);
|
|
1380
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1381
|
+
let changed = false;
|
|
1382
|
+
const nextMessages = currentMessages.map((message) => {
|
|
1383
|
+
const exactMatch = freshById.get(message.id);
|
|
1384
|
+
const correlatedMatch = exactMatch ? null : getCorrelationKeys(message).map((key) => freshByCorrelation.get(key)).find((candidate) => candidate !== null && candidate !== void 0 && !seen.has(candidate.id));
|
|
1385
|
+
const fresh = exactMatch ?? correlatedMatch;
|
|
1386
|
+
if (!fresh) return message;
|
|
1387
|
+
seen.add(fresh.id);
|
|
1388
|
+
if (getMessageSignature(message) === getMessageSignature(fresh)) {
|
|
1389
|
+
return message;
|
|
1390
|
+
}
|
|
1391
|
+
changed = true;
|
|
1392
|
+
return fresh;
|
|
1393
|
+
});
|
|
1394
|
+
const appended = freshMessages.filter((message) => !seen.has(message.id));
|
|
1395
|
+
if (appended.length > 0) {
|
|
1396
|
+
changed = true;
|
|
1397
|
+
nextMessages.push(...appended);
|
|
1398
|
+
nextMessages.sort((a, b) => {
|
|
1399
|
+
const timestampDelta = (a.timestamp ?? 0) - (b.timestamp ?? 0);
|
|
1400
|
+
return timestampDelta !== 0 ? timestampDelta : a.id.localeCompare(b.id);
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
return changed ? { messages: nextMessages, changed } : { messages: currentMessages, changed: false };
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1244
1406
|
// src/toolActivity.ts
|
|
1245
1407
|
var fail = (message) => {
|
|
1246
1408
|
throw new ContractViolation(message);
|
|
@@ -1263,7 +1425,7 @@ var formatToolError = (error) => {
|
|
|
1263
1425
|
}
|
|
1264
1426
|
};
|
|
1265
1427
|
var expectToolArguments = (value, path) => {
|
|
1266
|
-
if (
|
|
1428
|
+
if (isRecord2(value)) return value;
|
|
1267
1429
|
if (typeof value === "string") {
|
|
1268
1430
|
try {
|
|
1269
1431
|
const parsed = JSON.parse(value);
|
|
@@ -1439,9 +1601,6 @@ var canAttachToStreamingAssistant = (message, incomingAgentKey) => {
|
|
|
1439
1601
|
const currentAgentKey = messageAgentKey(message);
|
|
1440
1602
|
return !incomingAgentKey || !currentAgentKey || currentAgentKey === incomingAgentKey;
|
|
1441
1603
|
};
|
|
1442
|
-
var canAttachToCurrentStreamingAssistant = (message) => {
|
|
1443
|
-
return !!message && message.role === "assistant" && message.isStreaming === true;
|
|
1444
|
-
};
|
|
1445
1604
|
|
|
1446
1605
|
// src/messageContract.ts
|
|
1447
1606
|
var isInternalMessageMetadata = (metadata) => metadata?.visibility === "internal";
|
|
@@ -1548,9 +1707,10 @@ var convertServerMessage = (msg, options = {}) => {
|
|
|
1548
1707
|
const isToolSender = msg.senderType === "tool";
|
|
1549
1708
|
const content = isToolSender ? "" : messageContent;
|
|
1550
1709
|
const reasoning = typeof msg.reasoning === "string" && msg.reasoning.length > 0 ? msg.reasoning : void 0;
|
|
1710
|
+
const llmAttemptId = typeof metadata?.[LLM_ATTEMPT_ID_METADATA_KEY] === "string" ? metadata[LLM_ATTEMPT_ID_METADATA_KEY].trim() : "";
|
|
1551
1711
|
const activityItems = [
|
|
1552
1712
|
...reasoning ? [{
|
|
1553
|
-
id: `${msg.id}:
|
|
1713
|
+
id: `${llmAttemptId || msg.id}:reasoning:0`,
|
|
1554
1714
|
kind: "thinking",
|
|
1555
1715
|
status: "complete",
|
|
1556
1716
|
completedAt: timestamp,
|
|
@@ -1631,27 +1791,6 @@ var prepareHydratedMessages = async (rawMessages, options = {}) => {
|
|
|
1631
1791
|
};
|
|
1632
1792
|
};
|
|
1633
1793
|
|
|
1634
|
-
// src/streamEvents.ts
|
|
1635
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1636
|
-
var hasValues = (value) => Array.isArray(value) && value.length > 0;
|
|
1637
|
-
var getStreamEventPayload = (event) => {
|
|
1638
|
-
if (!isRecord2(event)) return event;
|
|
1639
|
-
return "payload" in event ? event.payload : event;
|
|
1640
|
-
};
|
|
1641
|
-
var isTerminalEmptyLlmResultEvent = (event) => {
|
|
1642
|
-
if (!isRecord2(event) || event.type !== "LLM_RESULT") return false;
|
|
1643
|
-
const payload = getStreamEventPayload(event);
|
|
1644
|
-
if (!isRecord2(payload)) return false;
|
|
1645
|
-
if (payload.answer !== "") return false;
|
|
1646
|
-
if (payload.finishReason === "tool_calls") return false;
|
|
1647
|
-
if (hasValues(payload.toolCalls)) return false;
|
|
1648
|
-
const metadata = isRecord2(event.metadata) ? event.metadata : {};
|
|
1649
|
-
const routing = isRecord2(metadata.routing) ? metadata.routing : {};
|
|
1650
|
-
if ((routing.action === "ask" || routing.action === "handoff") && typeof routing.targetId === "string" && routing.targetId.trim().length > 0) return false;
|
|
1651
|
-
if (hasValues(metadata.targetQueue)) return false;
|
|
1652
|
-
return true;
|
|
1653
|
-
};
|
|
1654
|
-
|
|
1655
1794
|
// src/threadIdentity.ts
|
|
1656
1795
|
var identityValues = ({ id, externalId }) => [id, externalId].filter((value) => typeof value === "string" && value.length > 0);
|
|
1657
1796
|
var isSameThreadIdentity = (owner, current) => {
|
|
@@ -1661,12 +1800,256 @@ var isSameThreadIdentity = (owner, current) => {
|
|
|
1661
1800
|
return ownerValues.some((value) => currentValues.has(value));
|
|
1662
1801
|
};
|
|
1663
1802
|
|
|
1803
|
+
// src/liveRun.ts
|
|
1804
|
+
var copyState = (state) => ({
|
|
1805
|
+
...state,
|
|
1806
|
+
attemptsById: new Map(state.attemptsById),
|
|
1807
|
+
toolMessageByCallId: new Map(state.toolMessageByCallId),
|
|
1808
|
+
pendingToolResultsByCallId: new Map(state.pendingToolResultsByCallId)
|
|
1809
|
+
});
|
|
1810
|
+
var createLiveRunState = (initialMessageId) => ({
|
|
1811
|
+
initialMessageId,
|
|
1812
|
+
initialMessageClaimed: false,
|
|
1813
|
+
activeAttemptId: null,
|
|
1814
|
+
lastAttemptId: null,
|
|
1815
|
+
attemptsById: /* @__PURE__ */ new Map(),
|
|
1816
|
+
toolMessageByCallId: /* @__PURE__ */ new Map(),
|
|
1817
|
+
pendingToolResultsByCallId: /* @__PURE__ */ new Map()
|
|
1818
|
+
});
|
|
1819
|
+
var ensureAttempt = (state, attemptId, sender, at, options) => {
|
|
1820
|
+
const existing = state.attemptsById.get(attemptId);
|
|
1821
|
+
if (existing) {
|
|
1822
|
+
const next2 = copyState(state);
|
|
1823
|
+
const cursor2 = sender && existing.sender !== sender ? { ...existing, sender } : existing;
|
|
1824
|
+
next2.attemptsById.set(attemptId, cursor2);
|
|
1825
|
+
next2.activeAttemptId = attemptId;
|
|
1826
|
+
next2.lastAttemptId = attemptId;
|
|
1827
|
+
return { state: next2, cursor: cursor2, operations: [] };
|
|
1828
|
+
}
|
|
1829
|
+
const next = copyState(state);
|
|
1830
|
+
const messageId = next.initialMessageClaimed ? options.createId() : next.initialMessageId;
|
|
1831
|
+
const cursor = { messageId, sender };
|
|
1832
|
+
next.initialMessageClaimed = true;
|
|
1833
|
+
next.activeAttemptId = attemptId;
|
|
1834
|
+
next.lastAttemptId = attemptId;
|
|
1835
|
+
next.attemptsById.set(attemptId, cursor);
|
|
1836
|
+
return {
|
|
1837
|
+
state: next,
|
|
1838
|
+
cursor,
|
|
1839
|
+
operations: [{
|
|
1840
|
+
type: "ensure-attempt",
|
|
1841
|
+
messageId,
|
|
1842
|
+
attemptId,
|
|
1843
|
+
sender,
|
|
1844
|
+
at
|
|
1845
|
+
}]
|
|
1846
|
+
};
|
|
1847
|
+
};
|
|
1848
|
+
var transitionLiveRun = (state, action, options) => {
|
|
1849
|
+
if (action.type === "attempt-start") {
|
|
1850
|
+
const ensured = ensureAttempt(
|
|
1851
|
+
state,
|
|
1852
|
+
action.attemptId,
|
|
1853
|
+
action.sender,
|
|
1854
|
+
action.at,
|
|
1855
|
+
options
|
|
1856
|
+
);
|
|
1857
|
+
return { state: ensured.state, operations: ensured.operations };
|
|
1858
|
+
}
|
|
1859
|
+
if (action.type === "token") {
|
|
1860
|
+
const ensured = ensureAttempt(
|
|
1861
|
+
state,
|
|
1862
|
+
action.attemptId,
|
|
1863
|
+
action.sender,
|
|
1864
|
+
action.at,
|
|
1865
|
+
options
|
|
1866
|
+
);
|
|
1867
|
+
return {
|
|
1868
|
+
state: ensured.state,
|
|
1869
|
+
operations: [
|
|
1870
|
+
...ensured.operations,
|
|
1871
|
+
{
|
|
1872
|
+
type: "update-token",
|
|
1873
|
+
messageId: ensured.cursor.messageId,
|
|
1874
|
+
phaseId: action.phaseId,
|
|
1875
|
+
partial: action.partial,
|
|
1876
|
+
isReasoning: action.isReasoning,
|
|
1877
|
+
sender: action.sender ?? ensured.cursor.sender,
|
|
1878
|
+
at: action.at
|
|
1879
|
+
}
|
|
1880
|
+
]
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
if (action.type === "attempt-result") {
|
|
1884
|
+
const cursor = state.attemptsById.get(action.attemptId);
|
|
1885
|
+
if (!cursor) return { state, operations: [] };
|
|
1886
|
+
const next = copyState(state);
|
|
1887
|
+
if (next.activeAttemptId === action.attemptId) {
|
|
1888
|
+
next.activeAttemptId = null;
|
|
1889
|
+
}
|
|
1890
|
+
next.lastAttemptId = action.attemptId;
|
|
1891
|
+
return {
|
|
1892
|
+
state: next,
|
|
1893
|
+
operations: [{
|
|
1894
|
+
type: "finalize-attempt",
|
|
1895
|
+
messageId: cursor.messageId,
|
|
1896
|
+
answer: action.answer,
|
|
1897
|
+
at: action.at
|
|
1898
|
+
}]
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
if (action.type === "tool-call") {
|
|
1902
|
+
if (state.toolMessageByCallId.has(action.toolCall.id)) {
|
|
1903
|
+
return { state, operations: [] };
|
|
1904
|
+
}
|
|
1905
|
+
const attemptId = action.attemptId ?? state.activeAttemptId ?? state.lastAttemptId ?? `tool-attempt:${action.toolCall.id}`;
|
|
1906
|
+
const ensured = ensureAttempt(
|
|
1907
|
+
state,
|
|
1908
|
+
attemptId,
|
|
1909
|
+
action.sender,
|
|
1910
|
+
action.at,
|
|
1911
|
+
options
|
|
1912
|
+
);
|
|
1913
|
+
const next = copyState(ensured.state);
|
|
1914
|
+
next.toolMessageByCallId.set(action.toolCall.id, ensured.cursor.messageId);
|
|
1915
|
+
const pendingResult = next.pendingToolResultsByCallId.get(action.toolCall.id);
|
|
1916
|
+
if (pendingResult) {
|
|
1917
|
+
next.pendingToolResultsByCallId.delete(action.toolCall.id);
|
|
1918
|
+
}
|
|
1919
|
+
return {
|
|
1920
|
+
state: next,
|
|
1921
|
+
operations: [
|
|
1922
|
+
...ensured.operations,
|
|
1923
|
+
{
|
|
1924
|
+
type: "append-tool",
|
|
1925
|
+
messageId: ensured.cursor.messageId,
|
|
1926
|
+
toolCall: action.toolCall,
|
|
1927
|
+
sender: action.sender ?? ensured.cursor.sender,
|
|
1928
|
+
at: action.at
|
|
1929
|
+
},
|
|
1930
|
+
...pendingResult ? [{
|
|
1931
|
+
type: "resolve-tool",
|
|
1932
|
+
messageId: ensured.cursor.messageId,
|
|
1933
|
+
update: pendingResult
|
|
1934
|
+
}] : []
|
|
1935
|
+
]
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
const messageId = action.update.id ? state.toolMessageByCallId.get(action.update.id) : void 0;
|
|
1939
|
+
if (!messageId || !action.update.id) {
|
|
1940
|
+
if (!action.update.id) return { state, operations: [] };
|
|
1941
|
+
const next = copyState(state);
|
|
1942
|
+
next.pendingToolResultsByCallId.set(action.update.id, action.update);
|
|
1943
|
+
return { state: next, operations: [] };
|
|
1944
|
+
}
|
|
1945
|
+
return {
|
|
1946
|
+
state,
|
|
1947
|
+
operations: [{ type: "resolve-tool", messageId, update: action.update }]
|
|
1948
|
+
};
|
|
1949
|
+
};
|
|
1950
|
+
var applyOperation = (messages, operation) => {
|
|
1951
|
+
const index = messages.findIndex((message) => message.id === operation.messageId);
|
|
1952
|
+
if (operation.type === "ensure-attempt") {
|
|
1953
|
+
if (index >= 0) {
|
|
1954
|
+
const current2 = messages[index];
|
|
1955
|
+
const nextMessage = {
|
|
1956
|
+
...current2,
|
|
1957
|
+
metadata: {
|
|
1958
|
+
...current2.metadata ?? {},
|
|
1959
|
+
[LLM_ATTEMPT_ID_METADATA_KEY]: operation.attemptId
|
|
1960
|
+
},
|
|
1961
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
1962
|
+
};
|
|
1963
|
+
if (current2.metadata?.[LLM_ATTEMPT_ID_METADATA_KEY] === operation.attemptId && (!operation.sender || current2.sender === operation.sender)) return messages;
|
|
1964
|
+
const next2 = [...messages];
|
|
1965
|
+
next2[index] = nextMessage;
|
|
1966
|
+
return next2;
|
|
1967
|
+
}
|
|
1968
|
+
return [
|
|
1969
|
+
...messages,
|
|
1970
|
+
{
|
|
1971
|
+
id: operation.messageId,
|
|
1972
|
+
role: "assistant",
|
|
1973
|
+
content: "",
|
|
1974
|
+
timestamp: operation.at,
|
|
1975
|
+
isStreaming: true,
|
|
1976
|
+
isComplete: false,
|
|
1977
|
+
metadata: { [LLM_ATTEMPT_ID_METADATA_KEY]: operation.attemptId },
|
|
1978
|
+
activity: {
|
|
1979
|
+
items: [{
|
|
1980
|
+
id: `${operation.messageId}:pending`,
|
|
1981
|
+
kind: "answering",
|
|
1982
|
+
status: "active",
|
|
1983
|
+
startedAt: operation.at
|
|
1984
|
+
}]
|
|
1985
|
+
},
|
|
1986
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
1987
|
+
}
|
|
1988
|
+
];
|
|
1989
|
+
}
|
|
1990
|
+
if (index < 0) return messages;
|
|
1991
|
+
const current = messages[index];
|
|
1992
|
+
let updated;
|
|
1993
|
+
if (operation.type === "update-token") {
|
|
1994
|
+
updated = {
|
|
1995
|
+
...updateAssistantMessageToken(current, {
|
|
1996
|
+
partial: operation.partial,
|
|
1997
|
+
isReasoning: operation.isReasoning,
|
|
1998
|
+
activityId: operation.phaseId,
|
|
1999
|
+
at: operation.at
|
|
2000
|
+
}),
|
|
2001
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
2002
|
+
};
|
|
2003
|
+
} else if (operation.type === "finalize-attempt") {
|
|
2004
|
+
updated = finalizeAssistantMessage(current, operation.answer, operation.at);
|
|
2005
|
+
} else if (operation.type === "append-tool") {
|
|
2006
|
+
updated = {
|
|
2007
|
+
...appendAssistantToolCall(current, {
|
|
2008
|
+
...operation.toolCall,
|
|
2009
|
+
startTime: operation.toolCall.startTime ?? operation.at
|
|
2010
|
+
}),
|
|
2011
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
2012
|
+
};
|
|
2013
|
+
} else {
|
|
2014
|
+
const toolItem = current.activity?.items.find((item) => item.kind === "tool" && item.id === operation.update.id);
|
|
2015
|
+
if (!toolItem) return messages;
|
|
2016
|
+
updated = applyAssistantToolResult(current, {
|
|
2017
|
+
...operation.update.id ? { id: operation.update.id } : {},
|
|
2018
|
+
...operation.update.toolExecutionId ? { toolExecutionId: operation.update.toolExecutionId } : {},
|
|
2019
|
+
name: operation.update.name ?? toolItem.toolName ?? toolItem.id,
|
|
2020
|
+
status: operation.update.status,
|
|
2021
|
+
...operation.update.result !== void 0 ? { result: operation.update.result } : {},
|
|
2022
|
+
...operation.update.error !== void 0 ? { error: operation.update.error } : {},
|
|
2023
|
+
endTime: operation.update.endTime
|
|
2024
|
+
});
|
|
2025
|
+
const hasActiveTool = updated.activity?.items.some((item) => item.kind === "tool" && item.status === "active") ?? false;
|
|
2026
|
+
updated = {
|
|
2027
|
+
...updated,
|
|
2028
|
+
isStreaming: hasActiveTool,
|
|
2029
|
+
isComplete: !hasActiveTool
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
if (updated === current) return messages;
|
|
2033
|
+
const next = [...messages];
|
|
2034
|
+
next[index] = updated;
|
|
2035
|
+
return next;
|
|
2036
|
+
};
|
|
2037
|
+
var applyLiveRunOperations = (messages, operations) => operations.reduce(applyOperation, messages);
|
|
2038
|
+
var getLatestLiveRunMessageId = (state) => {
|
|
2039
|
+
const attemptId = state.activeAttemptId ?? state.lastAttemptId;
|
|
2040
|
+
return attemptId ? state.attemptsById.get(attemptId)?.messageId ?? state.initialMessageId : state.initialMessageId;
|
|
2041
|
+
};
|
|
2042
|
+
|
|
1664
2043
|
// src/useCopilotzChat.ts
|
|
1665
2044
|
var nowTs = () => Date.now();
|
|
1666
2045
|
var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1667
2046
|
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
1668
2047
|
var getEventPayload = (event) => getStreamEventPayload(event);
|
|
1669
2048
|
var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
|
|
2049
|
+
var getEventTimestamp = (event) => {
|
|
2050
|
+
const timestamp = typeof event?.createdAt === "string" ? new Date(event.createdAt).getTime() : NaN;
|
|
2051
|
+
return Number.isFinite(timestamp) ? timestamp : nowTs();
|
|
2052
|
+
};
|
|
1670
2053
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1671
2054
|
var normalizeThreadTag = (value) => {
|
|
1672
2055
|
if (!isRecord3(value)) return null;
|
|
@@ -1695,66 +2078,11 @@ var createEmptyMessagePageInfo = () => ({
|
|
|
1695
2078
|
oldestMessageId: null,
|
|
1696
2079
|
newestMessageId: null
|
|
1697
2080
|
});
|
|
1698
|
-
var
|
|
1699
|
-
if (value === void 0) return "";
|
|
1700
|
-
try {
|
|
1701
|
-
return JSON.stringify(value) ?? "";
|
|
1702
|
-
} catch {
|
|
1703
|
-
return String(value);
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1706
|
-
var getMessageSignature = (message) => JSON.stringify({
|
|
1707
|
-
id: message.id,
|
|
1708
|
-
role: message.role,
|
|
1709
|
-
content: message.content ?? "",
|
|
1710
|
-
isStreaming: message.isStreaming === true,
|
|
1711
|
-
isComplete: message.isComplete === true,
|
|
1712
|
-
attachments: stringifyForCompare(message.attachments),
|
|
1713
|
-
activity: stringifyForCompare(message.activity),
|
|
1714
|
-
metadata: stringifyForCompare(message.metadata),
|
|
1715
|
-
sender: message.sender ? {
|
|
1716
|
-
id: message.sender.id,
|
|
1717
|
-
type: message.sender.type,
|
|
1718
|
-
name: message.sender.name,
|
|
1719
|
-
agentId: message.sender.agentId
|
|
1720
|
-
} : null
|
|
1721
|
-
});
|
|
1722
|
-
var reconcileThreadMessages = (currentMessages, freshMessages) => {
|
|
1723
|
-
if (freshMessages.length === 0) {
|
|
1724
|
-
return { messages: currentMessages, changed: false };
|
|
1725
|
-
}
|
|
1726
|
-
if (currentMessages.length === 0) {
|
|
1727
|
-
return { messages: freshMessages, changed: true };
|
|
1728
|
-
}
|
|
1729
|
-
const freshById = new Map(freshMessages.map((message) => [message.id, message]));
|
|
1730
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1731
|
-
let changed = false;
|
|
1732
|
-
const nextMessages = currentMessages.map((message) => {
|
|
1733
|
-
const fresh = freshById.get(message.id);
|
|
1734
|
-
if (!fresh) return message;
|
|
1735
|
-
seen.add(message.id);
|
|
1736
|
-
if (getMessageSignature(message) === getMessageSignature(fresh)) {
|
|
1737
|
-
return message;
|
|
1738
|
-
}
|
|
1739
|
-
changed = true;
|
|
1740
|
-
return fresh;
|
|
1741
|
-
});
|
|
1742
|
-
const appended = freshMessages.filter((message) => !seen.has(message.id));
|
|
1743
|
-
if (appended.length > 0) {
|
|
1744
|
-
changed = true;
|
|
1745
|
-
nextMessages.push(...appended);
|
|
1746
|
-
nextMessages.sort((a, b) => {
|
|
1747
|
-
const timestampDelta = (a.timestamp ?? 0) - (b.timestamp ?? 0);
|
|
1748
|
-
return timestampDelta !== 0 ? timestampDelta : a.id.localeCompare(b.id);
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
return changed ? { messages: nextMessages, changed } : { messages: currentMessages, changed: false };
|
|
1752
|
-
};
|
|
1753
|
-
var createPendingAssistantActivity = () => ({
|
|
2081
|
+
var createPendingAssistantActivity = (messageId) => ({
|
|
1754
2082
|
items: [
|
|
1755
2083
|
{
|
|
1756
|
-
id:
|
|
1757
|
-
kind: "
|
|
2084
|
+
id: `${messageId}:pending`,
|
|
2085
|
+
kind: "answering",
|
|
1758
2086
|
status: "active",
|
|
1759
2087
|
startedAt: nowTs()
|
|
1760
2088
|
}
|
|
@@ -1797,7 +2125,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
1797
2125
|
assistantName
|
|
1798
2126
|
});
|
|
1799
2127
|
const persistedToolUpdatesRef = useRef2([]);
|
|
1800
|
-
const liveToolUpdatesRef = useRef2([]);
|
|
1801
2128
|
const stopRequestedRef = useRef2(false);
|
|
1802
2129
|
const recoveryPollGenerationRef = useRef2(0);
|
|
1803
2130
|
threadsRef.current = threads;
|
|
@@ -1993,7 +2320,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
1993
2320
|
setIsLoadingOlderMessages(false);
|
|
1994
2321
|
setMessagePageInfo(createEmptyMessagePageInfo());
|
|
1995
2322
|
persistedToolUpdatesRef.current = [];
|
|
1996
|
-
liveToolUpdatesRef.current = [];
|
|
1997
2323
|
try {
|
|
1998
2324
|
const page = await fetchThreadMessagesPage(threadId, { limit: THREAD_MESSAGES_PAGE_SIZE }, getRequestHeaders);
|
|
1999
2325
|
const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
|
|
@@ -2353,154 +2679,26 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2353
2679
|
}, []);
|
|
2354
2680
|
const sendCopilotzMessage = useCallback2(
|
|
2355
2681
|
async (params) => {
|
|
2356
|
-
|
|
2357
|
-
let
|
|
2682
|
+
const initialAssistantMessageId = params.assistantMessageId ?? generateId();
|
|
2683
|
+
let liveRunState = createLiveRunState(initialAssistantMessageId);
|
|
2358
2684
|
const streamOwnerThreadId = currentThreadIdRef.current;
|
|
2359
2685
|
const streamOwnerThreadExternalId = params.threadExternalId ?? currentThreadExternalIdRef.current;
|
|
2360
2686
|
const streamStillOwnsVisibleThread = () => isSameThreadIdentity(
|
|
2361
2687
|
{ id: streamOwnerThreadId, externalId: streamOwnerThreadExternalId },
|
|
2362
2688
|
{ id: currentThreadIdRef.current, externalId: currentThreadExternalIdRef.current }
|
|
2363
2689
|
);
|
|
2364
|
-
params.onBeforeStart?.(
|
|
2690
|
+
params.onBeforeStart?.(initialAssistantMessageId);
|
|
2365
2691
|
let hasStreamProgress = false;
|
|
2366
|
-
const
|
|
2367
|
-
if (!streamStillOwnsVisibleThread()) return;
|
|
2368
|
-
if (partial && partial.length > 0) {
|
|
2369
|
-
hasStreamProgress = true;
|
|
2370
|
-
setIsRecoveringStream(false);
|
|
2371
|
-
}
|
|
2372
|
-
const isReasoning = opts?.isReasoning ?? false;
|
|
2373
|
-
const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
|
|
2374
|
-
if (nextSender) {
|
|
2375
|
-
currentAssistantSender = nextSender;
|
|
2376
|
-
}
|
|
2377
|
-
const nextAgentKey = currentAssistantSender?.agentId ?? currentAssistantSender?.id ?? null;
|
|
2378
|
-
const applyUpdate = (msg) => {
|
|
2379
|
-
return {
|
|
2380
|
-
...updateAssistantMessageToken(msg, {
|
|
2381
|
-
partial,
|
|
2382
|
-
isReasoning
|
|
2383
|
-
}),
|
|
2384
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2385
|
-
};
|
|
2386
|
-
};
|
|
2387
|
-
setMessages((prev) => {
|
|
2388
|
-
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
2389
|
-
if (idx >= 0 && canAttachToCurrentStreamingAssistant(prev[idx])) {
|
|
2390
|
-
const msg = prev[idx];
|
|
2391
|
-
const next = applyUpdate(msg);
|
|
2392
|
-
if (msg.content === next.content && msg.activity === next.activity && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
|
|
2393
|
-
return prev;
|
|
2394
|
-
}
|
|
2395
|
-
const updated = [...prev];
|
|
2396
|
-
updated[idx] = next;
|
|
2397
|
-
return updated;
|
|
2398
|
-
}
|
|
2399
|
-
const last = prev[prev.length - 1];
|
|
2400
|
-
if (canAttachToStreamingAssistant(last, nextAgentKey)) {
|
|
2401
|
-
currentAssistantId = last.id;
|
|
2402
|
-
const next = applyUpdate(last);
|
|
2403
|
-
if (last.content === next.content && last.activity === next.activity && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
|
|
2404
|
-
return prev;
|
|
2405
|
-
}
|
|
2406
|
-
const updated = [...prev];
|
|
2407
|
-
updated[prev.length - 1] = next;
|
|
2408
|
-
return updated;
|
|
2409
|
-
}
|
|
2410
|
-
const lastStreamingBelongsToDifferentAgent = Boolean(nextAgentKey) && last?.role === "assistant" && last.isStreaming && Boolean(messageAgentKey(last)) && messageAgentKey(last) !== nextAgentKey;
|
|
2411
|
-
if (!prev.length || prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming || lastStreamingBelongsToDifferentAgent) {
|
|
2412
|
-
const newId = generateId();
|
|
2413
|
-
currentAssistantId = newId;
|
|
2414
|
-
const base = {
|
|
2415
|
-
id: newId,
|
|
2416
|
-
role: "assistant",
|
|
2417
|
-
content: "",
|
|
2418
|
-
timestamp: nowTs(),
|
|
2419
|
-
isStreaming: true,
|
|
2420
|
-
isComplete: false,
|
|
2421
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2422
|
-
};
|
|
2423
|
-
return [...prev, applyUpdate(base)];
|
|
2424
|
-
}
|
|
2425
|
-
return prev;
|
|
2426
|
-
});
|
|
2427
|
-
};
|
|
2428
|
-
const finalizeCurrentAssistantBubble = () => {
|
|
2429
|
-
if (!streamStillOwnsVisibleThread()) return;
|
|
2430
|
-
setMessages((prev) => {
|
|
2431
|
-
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
2432
|
-
if (idx < 0) return prev;
|
|
2433
|
-
const msg = prev[idx];
|
|
2434
|
-
if (!msg.isStreaming && msg.isComplete) return prev;
|
|
2435
|
-
const updated = [...prev];
|
|
2436
|
-
updated[idx] = closeAssistantMessage(msg);
|
|
2437
|
-
return updated;
|
|
2438
|
-
});
|
|
2439
|
-
};
|
|
2440
|
-
const curThreadId = currentThreadIdRef.current;
|
|
2441
|
-
const applyLiveToolResultUpdate = (update) => {
|
|
2692
|
+
const dispatchLiveRunAction = (action) => {
|
|
2442
2693
|
if (!streamStillOwnsVisibleThread()) return;
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
const next = applyToolResultUpdateToMessages(prev, update, {
|
|
2446
|
-
isStreaming: true,
|
|
2447
|
-
isComplete: false
|
|
2448
|
-
});
|
|
2449
|
-
matched = next.matched;
|
|
2450
|
-
return next.matched ? next.messages : prev;
|
|
2694
|
+
const transition = transitionLiveRun(liveRunState, action, {
|
|
2695
|
+
createId: generateId
|
|
2451
2696
|
});
|
|
2452
|
-
|
|
2453
|
-
|
|
2697
|
+
liveRunState = transition.state;
|
|
2698
|
+
if (transition.operations.length > 0) {
|
|
2699
|
+
setMessages((prev) => applyLiveRunOperations(prev, transition.operations));
|
|
2454
2700
|
}
|
|
2455
2701
|
};
|
|
2456
|
-
const finalizeActiveAssistantTurn = (finalAnswer) => {
|
|
2457
|
-
if (!streamStillOwnsVisibleThread()) return;
|
|
2458
|
-
setMessages((prev) => {
|
|
2459
|
-
const currentIdx = prev.findIndex((message2) => message2.id === currentAssistantId && message2.role === "assistant");
|
|
2460
|
-
const fallbackIdx = currentIdx >= 0 ? currentIdx : (() => {
|
|
2461
|
-
for (let i = prev.length - 1; i >= 0; i--) {
|
|
2462
|
-
if (prev[i].role === "assistant" && prev[i].isStreaming) {
|
|
2463
|
-
return i;
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
return -1;
|
|
2467
|
-
})();
|
|
2468
|
-
if (fallbackIdx < 0) return prev;
|
|
2469
|
-
const message = prev[fallbackIdx];
|
|
2470
|
-
const nextMessage = finalizeAssistantMessage(message, finalAnswer);
|
|
2471
|
-
if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity) {
|
|
2472
|
-
return prev;
|
|
2473
|
-
}
|
|
2474
|
-
const updated = [...prev];
|
|
2475
|
-
updated[fallbackIdx] = nextMessage;
|
|
2476
|
-
currentAssistantId = nextMessage.id;
|
|
2477
|
-
return updated;
|
|
2478
|
-
});
|
|
2479
|
-
};
|
|
2480
|
-
const toServerMessageFromEvent = async (event) => {
|
|
2481
|
-
if (!event) return null;
|
|
2482
|
-
const type = event?.type || "";
|
|
2483
|
-
const payload = event?.payload ?? event;
|
|
2484
|
-
if (type === "TOOL_CALL") {
|
|
2485
|
-
const parsedToolCall = extractLiveToolCall(payload);
|
|
2486
|
-
return {
|
|
2487
|
-
id: generateId(),
|
|
2488
|
-
threadId: curThreadId ?? "",
|
|
2489
|
-
senderType: "tool",
|
|
2490
|
-
content: "",
|
|
2491
|
-
toolCalls: [
|
|
2492
|
-
{
|
|
2493
|
-
id: parsedToolCall.id ?? generateId(),
|
|
2494
|
-
name: parsedToolCall.name,
|
|
2495
|
-
args: parsedToolCall.arguments,
|
|
2496
|
-
...parsedToolCall.result !== void 0 ? { output: parsedToolCall.result } : {},
|
|
2497
|
-
status: parsedToolCall.status
|
|
2498
|
-
}
|
|
2499
|
-
]
|
|
2500
|
-
};
|
|
2501
|
-
}
|
|
2502
|
-
return null;
|
|
2503
|
-
};
|
|
2504
2702
|
const abortController = new AbortController();
|
|
2505
2703
|
if (!params.preserveActiveRun) {
|
|
2506
2704
|
abortControllerRef.current?.abort();
|
|
@@ -2511,7 +2709,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2511
2709
|
stopRequestedRef.current = false;
|
|
2512
2710
|
setThreadActivityStatus("running");
|
|
2513
2711
|
setIsStreaming(true);
|
|
2514
|
-
liveToolUpdatesRef.current = [];
|
|
2515
2712
|
let streamError = null;
|
|
2516
2713
|
const resolveActivityThreadId = async () => {
|
|
2517
2714
|
if (params.threadId) return params.threadId;
|
|
@@ -2558,10 +2755,26 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2558
2755
|
participants: participantsRef.current,
|
|
2559
2756
|
targetAgent: targetAgentNameRef.current,
|
|
2560
2757
|
getRequestHeaders,
|
|
2561
|
-
onToken: (token, _isComplete, raw, opts) =>
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2758
|
+
onToken: (token, _isComplete, raw, opts) => {
|
|
2759
|
+
const rawAgent = raw?.payload?.agent ?? raw?.agent ?? null;
|
|
2760
|
+
const sender = rawAgent ? resolveAgentSender(rawAgent, senderOptionsRef.current) : params.assistantSender;
|
|
2761
|
+
const attemptId = opts?.llmAttemptId ?? liveRunState.activeAttemptId ?? liveRunState.lastAttemptId ?? `stream-attempt:${initialAssistantMessageId}`;
|
|
2762
|
+
const isReasoning = opts?.isReasoning ?? false;
|
|
2763
|
+
const phaseId = opts?.phaseId ?? `${attemptId}:${isReasoning ? "reasoning" : "answer"}:0`;
|
|
2764
|
+
if (token.length > 0) {
|
|
2765
|
+
hasStreamProgress = true;
|
|
2766
|
+
setIsRecoveringStream(false);
|
|
2767
|
+
}
|
|
2768
|
+
dispatchLiveRunAction({
|
|
2769
|
+
type: "token",
|
|
2770
|
+
attemptId,
|
|
2771
|
+
phaseId,
|
|
2772
|
+
partial: token,
|
|
2773
|
+
isReasoning,
|
|
2774
|
+
sender,
|
|
2775
|
+
at: getEventTimestamp(raw)
|
|
2776
|
+
});
|
|
2777
|
+
},
|
|
2565
2778
|
onMessageEvent: async (event) => {
|
|
2566
2779
|
if (!streamStillOwnsVisibleThread()) return;
|
|
2567
2780
|
const intercepted = applyEventInterceptor(event);
|
|
@@ -2570,14 +2783,37 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2570
2783
|
}
|
|
2571
2784
|
const type = event?.type || "";
|
|
2572
2785
|
const payload = getEventPayload(event);
|
|
2786
|
+
if (type === "LLM_CALL") {
|
|
2787
|
+
const attemptId = getLlmAttemptId(event);
|
|
2788
|
+
if (attemptId) {
|
|
2789
|
+
dispatchLiveRunAction({
|
|
2790
|
+
type: "attempt-start",
|
|
2791
|
+
attemptId,
|
|
2792
|
+
sender: resolveLiveEventSender(event, senderOptionsRef.current),
|
|
2793
|
+
at: getEventTimestamp(event)
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2573
2798
|
if (type === "TOOL_RESULT") {
|
|
2574
2799
|
processToolOutput(payload ?? {});
|
|
2575
|
-
|
|
2800
|
+
dispatchLiveRunAction({
|
|
2801
|
+
type: "tool-result",
|
|
2802
|
+
update: extractLiveToolResultUpdate(payload ?? {})
|
|
2803
|
+
});
|
|
2576
2804
|
return;
|
|
2577
2805
|
}
|
|
2578
2806
|
if (type === "LLM_RESULT") {
|
|
2579
2807
|
const finalAnswer = typeof payload?.answer === "string" ? payload.answer : void 0;
|
|
2580
|
-
|
|
2808
|
+
const attemptId = getLlmAttemptId(event) ?? liveRunState.activeAttemptId ?? liveRunState.lastAttemptId;
|
|
2809
|
+
if (attemptId) {
|
|
2810
|
+
dispatchLiveRunAction({
|
|
2811
|
+
type: "attempt-result",
|
|
2812
|
+
attemptId,
|
|
2813
|
+
answer: finalAnswer,
|
|
2814
|
+
at: getEventTimestamp(event)
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2581
2817
|
if (isTerminalEmptyLlmResultEvent(event)) {
|
|
2582
2818
|
finalizeStreamingPlaceholders();
|
|
2583
2819
|
}
|
|
@@ -2589,87 +2825,22 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2589
2825
|
if (type === "TOOL_CALL") {
|
|
2590
2826
|
const parsedToolCall = extractLiveToolCall(payload ?? {});
|
|
2591
2827
|
const eventSender = resolveLiveEventSender(event, senderOptionsRef.current);
|
|
2592
|
-
currentAssistantSender = eventSender;
|
|
2593
|
-
const eventAgentKey = currentAssistantSender.agentId ?? currentAssistantSender.id;
|
|
2594
2828
|
const callId = parsedToolCall.id ?? generateId();
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
if (!message) return false;
|
|
2609
|
-
return message.role === "assistant" && message.isStreaming && message.content.trim().length === 0 && !message.attachments?.length;
|
|
2610
|
-
};
|
|
2611
|
-
const appendToolCall = (msg) => ({
|
|
2612
|
-
...appendAssistantToolCall(msg, {
|
|
2613
|
-
id: callId,
|
|
2614
|
-
name: toolName,
|
|
2615
|
-
arguments: parsedToolCall.arguments,
|
|
2616
|
-
...initialResult !== void 0 ? { result: initialResult } : {},
|
|
2617
|
-
status: initialStatus,
|
|
2618
|
-
startTime: Date.now(),
|
|
2619
|
-
...endTime !== void 0 ? { endTime } : {}
|
|
2620
|
-
})
|
|
2621
|
-
});
|
|
2622
|
-
const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming && canHostActivity(message));
|
|
2623
|
-
if (currentIdx >= 0) {
|
|
2624
|
-
const next = [...prev];
|
|
2625
|
-
next[currentIdx] = appendToolCall({
|
|
2626
|
-
...next[currentIdx],
|
|
2627
|
-
isStreaming: true,
|
|
2628
|
-
isComplete: false,
|
|
2629
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2630
|
-
});
|
|
2631
|
-
return next;
|
|
2632
|
-
}
|
|
2633
|
-
const last = prev[prev.length - 1];
|
|
2634
|
-
if (canHostActivity(last) && canAttachToStreamingAssistant(last, eventAgentKey)) {
|
|
2635
|
-
currentAssistantId = last.id;
|
|
2636
|
-
const next = [...prev];
|
|
2637
|
-
next[prev.length - 1] = appendToolCall({
|
|
2638
|
-
...last,
|
|
2639
|
-
isStreaming: true,
|
|
2640
|
-
isComplete: false,
|
|
2641
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2642
|
-
});
|
|
2643
|
-
return next;
|
|
2644
|
-
}
|
|
2645
|
-
const newId = generateId();
|
|
2646
|
-
currentAssistantId = newId;
|
|
2647
|
-
return [
|
|
2648
|
-
...prev,
|
|
2649
|
-
appendToolCall({
|
|
2650
|
-
id: newId,
|
|
2651
|
-
role: "assistant",
|
|
2652
|
-
content: "",
|
|
2653
|
-
timestamp: nowTs(),
|
|
2654
|
-
isStreaming: true,
|
|
2655
|
-
isComplete: false,
|
|
2656
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2657
|
-
})
|
|
2658
|
-
];
|
|
2659
|
-
})()
|
|
2660
|
-
);
|
|
2661
|
-
hasStreamProgress = true;
|
|
2662
|
-
return;
|
|
2663
|
-
}
|
|
2664
|
-
const sm = await toServerMessageFromEvent(event);
|
|
2665
|
-
if (sm) {
|
|
2666
|
-
const viewMsg = convertServerMessage(sm, {
|
|
2667
|
-
senderOptions: senderOptionsRef.current,
|
|
2668
|
-
createId: generateId,
|
|
2669
|
-
now: nowTs
|
|
2829
|
+
dispatchLiveRunAction({
|
|
2830
|
+
type: "tool-call",
|
|
2831
|
+
attemptId: getLlmAttemptId(event) ?? liveRunState.activeAttemptId ?? liveRunState.lastAttemptId,
|
|
2832
|
+
sender: eventSender,
|
|
2833
|
+
at: getEventTimestamp(event),
|
|
2834
|
+
toolCall: {
|
|
2835
|
+
id: callId,
|
|
2836
|
+
name: parsedToolCall.name,
|
|
2837
|
+
arguments: parsedToolCall.arguments,
|
|
2838
|
+
status: parsedToolCall.status,
|
|
2839
|
+
...parsedToolCall.toolExecutionId ? { toolExecutionId: parsedToolCall.toolExecutionId } : {},
|
|
2840
|
+
...parsedToolCall.result !== void 0 ? { result: parsedToolCall.result } : {}
|
|
2841
|
+
}
|
|
2670
2842
|
});
|
|
2671
|
-
|
|
2672
|
-
setMessages((prev) => [...prev, viewMsg]);
|
|
2843
|
+
hasStreamProgress = true;
|
|
2673
2844
|
return;
|
|
2674
2845
|
}
|
|
2675
2846
|
handleStreamMessageEvent(event);
|
|
@@ -2683,10 +2854,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2683
2854
|
if (intercepted?.handled) {
|
|
2684
2855
|
return;
|
|
2685
2856
|
}
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
handleStreamAssetEvent(payload, currentAssistantId);
|
|
2689
|
-
})();
|
|
2857
|
+
if (!hasStreamProgress) return;
|
|
2858
|
+
handleStreamAssetEvent(payload, getLatestLiveRunMessageId(liveRunState));
|
|
2690
2859
|
},
|
|
2691
2860
|
signal: abortController.signal
|
|
2692
2861
|
});
|
|
@@ -2719,16 +2888,16 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2719
2888
|
abortControllerRef.current = null;
|
|
2720
2889
|
}
|
|
2721
2890
|
if (!streamStillOwnsVisibleThread()) {
|
|
2722
|
-
return
|
|
2891
|
+
return getLatestLiveRunMessageId(liveRunState);
|
|
2723
2892
|
}
|
|
2724
2893
|
if (recoveryStarted) {
|
|
2725
|
-
return
|
|
2894
|
+
return getLatestLiveRunMessageId(liveRunState);
|
|
2726
2895
|
}
|
|
2727
2896
|
finalizeStreamingPlaceholders();
|
|
2728
2897
|
if (streamError) {
|
|
2729
2898
|
throw streamError;
|
|
2730
2899
|
}
|
|
2731
|
-
return
|
|
2900
|
+
return getLatestLiveRunMessageId(liveRunState);
|
|
2732
2901
|
},
|
|
2733
2902
|
[applyEventInterceptor, handleStreamMessageEvent, handleStreamAssetEvent, fetchAndSetThreadsState, finalizeStreamingPlaceholders, getRequestHeaders, refreshThreadMessages, startThreadActivityRecovery]
|
|
2734
2903
|
);
|
|
@@ -2762,13 +2931,17 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2762
2931
|
);
|
|
2763
2932
|
const currentMetadata = threadMetadataMapRef.current[conversationKey];
|
|
2764
2933
|
const pendingTitle = currentMetadata?.pendingTitle;
|
|
2934
|
+
const userMessageId = generateId();
|
|
2765
2935
|
const userMessage = {
|
|
2766
|
-
id:
|
|
2936
|
+
id: userMessageId,
|
|
2767
2937
|
role: "user",
|
|
2768
2938
|
content,
|
|
2769
2939
|
timestamp,
|
|
2770
2940
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
2771
2941
|
isComplete: true,
|
|
2942
|
+
metadata: {
|
|
2943
|
+
[CLIENT_MESSAGE_ID_METADATA_KEY]: userMessageId
|
|
2944
|
+
},
|
|
2772
2945
|
sender: resolveUserSender({
|
|
2773
2946
|
id: userId,
|
|
2774
2947
|
name: getCurrentUserDisplayName(userName, userId)
|
|
@@ -2781,15 +2954,16 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2781
2954
|
},
|
|
2782
2955
|
senderOptionsRef.current
|
|
2783
2956
|
) : preferredAgentRef.current ? resolveAgentSender({ id: preferredAgentRef.current, name: preferredAgentRef.current }, senderOptionsRef.current) : resolveAssistantFallbackSender(senderOptionsRef.current);
|
|
2957
|
+
const assistantMessageId = generateId();
|
|
2784
2958
|
const assistantPlaceholder = {
|
|
2785
|
-
id:
|
|
2959
|
+
id: assistantMessageId,
|
|
2786
2960
|
role: "assistant",
|
|
2787
2961
|
content: "",
|
|
2788
2962
|
timestamp: timestamp + 1,
|
|
2789
2963
|
isStreaming: true,
|
|
2790
2964
|
isComplete: false,
|
|
2791
2965
|
sender: assistantSender,
|
|
2792
|
-
activity: createPendingAssistantActivity()
|
|
2966
|
+
activity: createPendingAssistantActivity(assistantMessageId)
|
|
2793
2967
|
};
|
|
2794
2968
|
setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);
|
|
2795
2969
|
setSpecialState(null);
|
|
@@ -2817,6 +2991,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2817
2991
|
userId,
|
|
2818
2992
|
userName: getCurrentUserDisplayName(userName, userId),
|
|
2819
2993
|
agentName: preferredAgentRef.current,
|
|
2994
|
+
metadata: userMessage.metadata,
|
|
2820
2995
|
assistantMessageId: assistantPlaceholder.id,
|
|
2821
2996
|
assistantSender,
|
|
2822
2997
|
preserveActiveRun,
|
|
@@ -2896,7 +3071,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2896
3071
|
isStreaming: true,
|
|
2897
3072
|
isComplete: false,
|
|
2898
3073
|
sender: assistantSender,
|
|
2899
|
-
activity: createPendingAssistantActivity()
|
|
3074
|
+
activity: createPendingAssistantActivity(assistantMessageId)
|
|
2900
3075
|
}
|
|
2901
3076
|
]);
|
|
2902
3077
|
setMessagePageInfo(createEmptyMessagePageInfo());
|