@copilotz/chat-adapter 0.9.44 → 0.9.46
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 +505 -386
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -101,6 +101,41 @@ 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.streamLlmAttemptId === "string" && metadata.streamLlmAttemptId.trim().length > 0) {
|
|
115
|
+
return metadata.streamLlmAttemptId.trim();
|
|
116
|
+
}
|
|
117
|
+
if (typeof metadata.llmAttemptId === "string" && metadata.llmAttemptId.trim().length > 0) {
|
|
118
|
+
return metadata.llmAttemptId.trim();
|
|
119
|
+
}
|
|
120
|
+
if (event.subjectType === "llm_attempt" && typeof event.subjectId === "string" && event.subjectId.trim().length > 0) {
|
|
121
|
+
return event.subjectId.trim();
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
var isTerminalEmptyLlmResultEvent = (event) => {
|
|
126
|
+
if (!isRecord(event) || event.type !== "LLM_RESULT") return false;
|
|
127
|
+
const payload = getStreamEventPayload(event);
|
|
128
|
+
if (!isRecord(payload)) return false;
|
|
129
|
+
if (payload.answer !== "") return false;
|
|
130
|
+
if (payload.finishReason === "tool_calls") return false;
|
|
131
|
+
if (hasValues(payload.toolCalls)) return false;
|
|
132
|
+
const metadata = isRecord(event.metadata) ? event.metadata : {};
|
|
133
|
+
const routing = isRecord(metadata.routing) ? metadata.routing : {};
|
|
134
|
+
if ((routing.action === "ask" || routing.action === "handoff") && typeof routing.targetId === "string" && routing.targetId.trim().length > 0) return false;
|
|
135
|
+
if (hasValues(metadata.targetQueue)) return false;
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
|
|
104
139
|
// src/copilotzService.ts
|
|
105
140
|
var rawBaseValue = import.meta.env?.VITE_API_URL;
|
|
106
141
|
var rawBase = typeof rawBaseValue === "string" && rawBaseValue.length > 0 ? rawBaseValue : "/api";
|
|
@@ -428,6 +463,11 @@ async function runCopilotzStream(options) {
|
|
|
428
463
|
let lastCompletedText = "";
|
|
429
464
|
let lastTokenWasReasoning = false;
|
|
430
465
|
let hadNonReasoningContent = false;
|
|
466
|
+
let activeLlmAttemptId = null;
|
|
467
|
+
let activePhaseKind = null;
|
|
468
|
+
let activePhaseId = null;
|
|
469
|
+
let phaseOrdinal = -1;
|
|
470
|
+
let fallbackAttemptOrdinal = 0;
|
|
431
471
|
const collectedMessages = [];
|
|
432
472
|
let collectedMedia = null;
|
|
433
473
|
const resetTokenAggregation = () => {
|
|
@@ -436,6 +476,30 @@ async function runCopilotzStream(options) {
|
|
|
436
476
|
lastTokenWasReasoning = false;
|
|
437
477
|
hadNonReasoningContent = false;
|
|
438
478
|
};
|
|
479
|
+
const startAttempt = (event) => {
|
|
480
|
+
activeLlmAttemptId = getLlmAttemptId(event) ?? `stream-attempt:${fallbackAttemptOrdinal++}`;
|
|
481
|
+
activePhaseKind = null;
|
|
482
|
+
activePhaseId = null;
|
|
483
|
+
phaseOrdinal = -1;
|
|
484
|
+
resetTokenAggregation();
|
|
485
|
+
};
|
|
486
|
+
const getTokenContext = (isReasoning) => {
|
|
487
|
+
if (!activeLlmAttemptId) {
|
|
488
|
+
startAttempt(null);
|
|
489
|
+
}
|
|
490
|
+
const kind = isReasoning ? "reasoning" : "answer";
|
|
491
|
+
if (activePhaseKind !== kind || !activePhaseId) {
|
|
492
|
+
phaseOrdinal += 1;
|
|
493
|
+
activePhaseKind = kind;
|
|
494
|
+
activePhaseId = `${activeLlmAttemptId}:${kind}:${phaseOrdinal}`;
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
isReasoning,
|
|
498
|
+
llmAttemptId: activeLlmAttemptId,
|
|
499
|
+
phaseId: activePhaseId,
|
|
500
|
+
phaseOrdinal
|
|
501
|
+
};
|
|
502
|
+
};
|
|
439
503
|
const processEvent = async (eventChunk) => {
|
|
440
504
|
if (!eventChunk.trim()) return;
|
|
441
505
|
const lines = eventChunk.split("\n");
|
|
@@ -461,10 +525,16 @@ async function runCopilotzStream(options) {
|
|
|
461
525
|
return;
|
|
462
526
|
}
|
|
463
527
|
switch (eventType) {
|
|
528
|
+
case "LLM_CALL": {
|
|
529
|
+
startAttempt(payload2);
|
|
530
|
+
await onMessageEvent?.(payload2);
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
464
533
|
case "TOKEN": {
|
|
465
534
|
const inner = payload2?.payload ?? payload2;
|
|
466
535
|
const chunk = typeof inner?.token === "string" ? inner.token : "";
|
|
467
|
-
const isReasoning =
|
|
536
|
+
const isReasoning = typeof inner?.isReasoning === "boolean" ? inner.isReasoning : activePhaseKind === "reasoning" || lastTokenWasReasoning;
|
|
537
|
+
const tokenContext = getTokenContext(isReasoning);
|
|
468
538
|
if (isReasoning && !lastTokenWasReasoning && hadNonReasoningContent) {
|
|
469
539
|
aggregatedReasoning = "";
|
|
470
540
|
aggregatedText = "";
|
|
@@ -482,12 +552,14 @@ async function runCopilotzStream(options) {
|
|
|
482
552
|
const isComplete = Boolean(inner?.isComplete);
|
|
483
553
|
if (chunk || isComplete) {
|
|
484
554
|
const tokenText = isReasoning ? aggregatedReasoning : aggregatedText;
|
|
485
|
-
onToken?.(tokenText, isComplete, payload2,
|
|
555
|
+
onToken?.(tokenText, isComplete, payload2, tokenContext);
|
|
486
556
|
if (isComplete) {
|
|
487
557
|
if (!isReasoning && tokenText) {
|
|
488
558
|
lastCompletedText = tokenText;
|
|
489
559
|
}
|
|
490
560
|
resetTokenAggregation();
|
|
561
|
+
activePhaseKind = null;
|
|
562
|
+
activePhaseId = null;
|
|
491
563
|
}
|
|
492
564
|
}
|
|
493
565
|
break;
|
|
@@ -833,8 +905,6 @@ function useUrlState(config = {}) {
|
|
|
833
905
|
}
|
|
834
906
|
|
|
835
907
|
// src/activity.ts
|
|
836
|
-
var thinkingId = "thinking";
|
|
837
|
-
var answeringId = "answering";
|
|
838
908
|
var getItems = (message) => Array.isArray(message.activity?.items) ? message.activity.items : [];
|
|
839
909
|
var setItems = (message, items) => ({
|
|
840
910
|
...message,
|
|
@@ -860,7 +930,8 @@ var upsertItem = (message, item) => {
|
|
|
860
930
|
};
|
|
861
931
|
return setItems(message, next);
|
|
862
932
|
};
|
|
863
|
-
var completeItems = (message, shouldComplete) => setItems(message, getItems(message).map((item) => item.status === "active" && shouldComplete(item) ? { ...item, status: "complete", completedAt
|
|
933
|
+
var completeItems = (message, shouldComplete, completedAt = Date.now()) => setItems(message, getItems(message).map((item) => item.status === "active" && shouldComplete(item) ? { ...item, status: "complete", completedAt } : item));
|
|
934
|
+
var removeItems = (message, shouldRemove) => setItems(message, getItems(message).filter((item) => !shouldRemove(item)));
|
|
864
935
|
var hasVisibleAssistantOutput = (message) => {
|
|
865
936
|
if (message.role !== "assistant") return false;
|
|
866
937
|
if (typeof message.content === "string" && message.content.trim().length > 0) return true;
|
|
@@ -875,38 +946,40 @@ var toPublicChatMessage = (message) => {
|
|
|
875
946
|
var updateAssistantMessageToken = (message, params) => {
|
|
876
947
|
if (message.role !== "assistant") return message;
|
|
877
948
|
if (params.isReasoning) {
|
|
878
|
-
|
|
949
|
+
const activityId2 = params.activityId ?? `${message.id}:thinking`;
|
|
950
|
+
return upsertItem(completeItems(removeItems({
|
|
879
951
|
...message,
|
|
880
952
|
isStreaming: true,
|
|
881
953
|
isComplete: false
|
|
882
|
-
}, {
|
|
883
|
-
id:
|
|
954
|
+
}, (item) => item.kind === "answering"), (item) => item.id !== activityId2 && (item.kind === "thinking" || item.kind === "answering"), params.at), {
|
|
955
|
+
id: activityId2,
|
|
884
956
|
kind: "thinking",
|
|
885
957
|
status: "active",
|
|
886
|
-
startedAt: getItems(message).find((item) => item.id ===
|
|
958
|
+
startedAt: getItems(message).find((item) => item.id === activityId2)?.startedAt ?? params.at ?? Date.now(),
|
|
887
959
|
details: { reasoning: params.partial }
|
|
888
960
|
});
|
|
889
961
|
}
|
|
890
|
-
|
|
962
|
+
const activityId = params.activityId ?? `${message.id}:answering`;
|
|
963
|
+
return upsertItem(completeItems(removeItems({
|
|
891
964
|
...message,
|
|
892
965
|
content: params.partial,
|
|
893
966
|
isStreaming: true,
|
|
894
967
|
isComplete: false
|
|
895
|
-
}, (item) => item.kind === "thinking" || item.kind === "
|
|
896
|
-
id:
|
|
968
|
+
}, (item) => item.kind === "answering" && item.id !== activityId), (item) => item.kind === "thinking" || item.kind === "answering", params.at), {
|
|
969
|
+
id: activityId,
|
|
897
970
|
kind: "answering",
|
|
898
971
|
status: "active",
|
|
899
|
-
startedAt: getItems(message).find((item) => item.id ===
|
|
972
|
+
startedAt: getItems(message).find((item) => item.id === activityId)?.startedAt ?? params.at ?? Date.now()
|
|
900
973
|
});
|
|
901
974
|
};
|
|
902
975
|
var appendAssistantToolCall = (message, toolCall) => {
|
|
903
976
|
if (message.role !== "assistant") return message;
|
|
904
977
|
const status = toolStatusToActivityStatus(toolCall.status);
|
|
905
|
-
return upsertItem({
|
|
978
|
+
return upsertItem(completeItems(removeItems({
|
|
906
979
|
...message,
|
|
907
980
|
isStreaming: true,
|
|
908
981
|
isComplete: false
|
|
909
|
-
}, {
|
|
982
|
+
}, (item) => item.kind === "answering"), (item) => item.kind === "thinking" || item.kind === "answering"), {
|
|
910
983
|
id: toolCall.id,
|
|
911
984
|
kind: "tool",
|
|
912
985
|
status,
|
|
@@ -955,23 +1028,23 @@ var applyAssistantToolResult = (message, update) => {
|
|
|
955
1028
|
};
|
|
956
1029
|
return setItems(message, next);
|
|
957
1030
|
};
|
|
958
|
-
var finalizeAssistantMessage = (message, finalAnswer) => {
|
|
1031
|
+
var finalizeAssistantMessage = (message, finalAnswer, completedAt = Date.now()) => {
|
|
959
1032
|
if (message.role !== "assistant") return message;
|
|
960
1033
|
const completed = completeItems({
|
|
961
1034
|
...message,
|
|
962
1035
|
...typeof finalAnswer === "string" && finalAnswer.length > 0 ? { content: finalAnswer } : {},
|
|
963
1036
|
isStreaming: false,
|
|
964
1037
|
isComplete: true
|
|
965
|
-
}, (item) => item.kind === "thinking" || item.kind === "answering");
|
|
1038
|
+
}, (item) => item.kind === "thinking" || item.kind === "answering", completedAt);
|
|
966
1039
|
return setItems(completed, getItems(completed).filter((item) => item.kind !== "answering"));
|
|
967
1040
|
};
|
|
968
|
-
var closeAssistantMessage = (message) => {
|
|
1041
|
+
var closeAssistantMessage = (message, completedAt = Date.now()) => {
|
|
969
1042
|
if (message.role !== "assistant") return message;
|
|
970
1043
|
return completeItems({
|
|
971
1044
|
...message,
|
|
972
1045
|
isStreaming: false,
|
|
973
1046
|
isComplete: true
|
|
974
|
-
}, () => true);
|
|
1047
|
+
}, () => true, completedAt);
|
|
975
1048
|
};
|
|
976
1049
|
|
|
977
1050
|
// src/contract.ts
|
|
@@ -981,9 +1054,9 @@ var ContractViolation = class extends Error {
|
|
|
981
1054
|
this.name = "ContractViolation";
|
|
982
1055
|
}
|
|
983
1056
|
};
|
|
984
|
-
var
|
|
1057
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
985
1058
|
var expectRecord = (value, path) => {
|
|
986
|
-
if (!
|
|
1059
|
+
if (!isRecord2(value)) throw new ContractViolation(`${path} must be an object`);
|
|
987
1060
|
return value;
|
|
988
1061
|
};
|
|
989
1062
|
var expectString = (value, path) => {
|
|
@@ -1104,14 +1177,14 @@ var resolveLiveEventSender = (event, options = {}) => {
|
|
|
1104
1177
|
const raw = expectRecord(event, "stream event");
|
|
1105
1178
|
const payload = raw.payload === void 0 ? raw : expectRecord(raw.payload, "stream event.payload");
|
|
1106
1179
|
const agent = payload.agent ?? raw.agent;
|
|
1107
|
-
if (
|
|
1180
|
+
if (isRecord2(agent)) {
|
|
1108
1181
|
return resolveAgentSender({
|
|
1109
1182
|
id: expectString(agent.id, "stream event.payload.agent.id"),
|
|
1110
1183
|
name: expectString(agent.name, "stream event.payload.agent.name")
|
|
1111
1184
|
}, options);
|
|
1112
1185
|
}
|
|
1113
1186
|
const sender = payload.sender ?? raw.sender;
|
|
1114
|
-
if (!
|
|
1187
|
+
if (!isRecord2(sender)) {
|
|
1115
1188
|
throw new ContractViolation("stream event sender contract requires payload.agent or payload.sender");
|
|
1116
1189
|
}
|
|
1117
1190
|
const type = expectSenderType(sender.type ?? payload.senderType, "stream event.payload.sender.type");
|
|
@@ -1241,6 +1314,98 @@ async function resolveAssetsInMessages(messages, getRequestHeaders) {
|
|
|
1241
1314
|
}));
|
|
1242
1315
|
}
|
|
1243
1316
|
|
|
1317
|
+
// src/messageReconciliation.ts
|
|
1318
|
+
var CLIENT_MESSAGE_ID_METADATA_KEY = "clientMessageId";
|
|
1319
|
+
var LLM_ATTEMPT_ID_METADATA_KEY = "llmAttemptId";
|
|
1320
|
+
var stringifyForCompare = (value) => {
|
|
1321
|
+
if (value === void 0) return "";
|
|
1322
|
+
try {
|
|
1323
|
+
return JSON.stringify(value) ?? "";
|
|
1324
|
+
} catch {
|
|
1325
|
+
return String(value);
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
var getMessageSignature = (message) => JSON.stringify({
|
|
1329
|
+
id: message.id,
|
|
1330
|
+
role: message.role,
|
|
1331
|
+
content: message.content ?? "",
|
|
1332
|
+
isStreaming: message.isStreaming === true,
|
|
1333
|
+
isComplete: message.isComplete === true,
|
|
1334
|
+
attachments: stringifyForCompare(message.attachments),
|
|
1335
|
+
activity: stringifyForCompare(message.activity),
|
|
1336
|
+
metadata: stringifyForCompare(message.metadata),
|
|
1337
|
+
sender: message.sender ? {
|
|
1338
|
+
id: message.sender.id,
|
|
1339
|
+
type: message.sender.type,
|
|
1340
|
+
name: message.sender.name,
|
|
1341
|
+
agentId: message.sender.agentId
|
|
1342
|
+
} : null
|
|
1343
|
+
});
|
|
1344
|
+
var getMetadataString = (message, key) => {
|
|
1345
|
+
const value = message.metadata?.[key];
|
|
1346
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1347
|
+
};
|
|
1348
|
+
var getCorrelationKeys = (message) => {
|
|
1349
|
+
const clientMessageId = getMetadataString(
|
|
1350
|
+
message,
|
|
1351
|
+
CLIENT_MESSAGE_ID_METADATA_KEY
|
|
1352
|
+
);
|
|
1353
|
+
const llmAttemptId = getMetadataString(
|
|
1354
|
+
message,
|
|
1355
|
+
LLM_ATTEMPT_ID_METADATA_KEY
|
|
1356
|
+
);
|
|
1357
|
+
return [
|
|
1358
|
+
...clientMessageId ? [`client-message:${message.role}:${clientMessageId}`] : [],
|
|
1359
|
+
...llmAttemptId ? [`llm-attempt:${message.role}:${llmAttemptId}`] : []
|
|
1360
|
+
];
|
|
1361
|
+
};
|
|
1362
|
+
var indexUniqueFreshCorrelations = (freshMessages) => {
|
|
1363
|
+
const messagesByCorrelation = /* @__PURE__ */ new Map();
|
|
1364
|
+
for (const message of freshMessages) {
|
|
1365
|
+
for (const key of getCorrelationKeys(message)) {
|
|
1366
|
+
messagesByCorrelation.set(
|
|
1367
|
+
key,
|
|
1368
|
+
messagesByCorrelation.has(key) ? null : message
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return messagesByCorrelation;
|
|
1373
|
+
};
|
|
1374
|
+
var reconcileThreadMessages = (currentMessages, freshMessages) => {
|
|
1375
|
+
if (freshMessages.length === 0) {
|
|
1376
|
+
return { messages: currentMessages, changed: false };
|
|
1377
|
+
}
|
|
1378
|
+
if (currentMessages.length === 0) {
|
|
1379
|
+
return { messages: freshMessages, changed: true };
|
|
1380
|
+
}
|
|
1381
|
+
const freshById = new Map(freshMessages.map((message) => [message.id, message]));
|
|
1382
|
+
const freshByCorrelation = indexUniqueFreshCorrelations(freshMessages);
|
|
1383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1384
|
+
let changed = false;
|
|
1385
|
+
const nextMessages = currentMessages.map((message) => {
|
|
1386
|
+
const exactMatch = freshById.get(message.id);
|
|
1387
|
+
const correlatedMatch = exactMatch ? null : getCorrelationKeys(message).map((key) => freshByCorrelation.get(key)).find((candidate) => candidate !== null && candidate !== void 0 && !seen.has(candidate.id));
|
|
1388
|
+
const fresh = exactMatch ?? correlatedMatch;
|
|
1389
|
+
if (!fresh) return message;
|
|
1390
|
+
seen.add(fresh.id);
|
|
1391
|
+
if (getMessageSignature(message) === getMessageSignature(fresh)) {
|
|
1392
|
+
return message;
|
|
1393
|
+
}
|
|
1394
|
+
changed = true;
|
|
1395
|
+
return fresh;
|
|
1396
|
+
});
|
|
1397
|
+
const appended = freshMessages.filter((message) => !seen.has(message.id));
|
|
1398
|
+
if (appended.length > 0) {
|
|
1399
|
+
changed = true;
|
|
1400
|
+
nextMessages.push(...appended);
|
|
1401
|
+
nextMessages.sort((a, b) => {
|
|
1402
|
+
const timestampDelta = (a.timestamp ?? 0) - (b.timestamp ?? 0);
|
|
1403
|
+
return timestampDelta !== 0 ? timestampDelta : a.id.localeCompare(b.id);
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
return changed ? { messages: nextMessages, changed } : { messages: currentMessages, changed: false };
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1244
1409
|
// src/toolActivity.ts
|
|
1245
1410
|
var fail = (message) => {
|
|
1246
1411
|
throw new ContractViolation(message);
|
|
@@ -1263,7 +1428,7 @@ var formatToolError = (error) => {
|
|
|
1263
1428
|
}
|
|
1264
1429
|
};
|
|
1265
1430
|
var expectToolArguments = (value, path) => {
|
|
1266
|
-
if (
|
|
1431
|
+
if (isRecord2(value)) return value;
|
|
1267
1432
|
if (typeof value === "string") {
|
|
1268
1433
|
try {
|
|
1269
1434
|
const parsed = JSON.parse(value);
|
|
@@ -1439,9 +1604,6 @@ var canAttachToStreamingAssistant = (message, incomingAgentKey) => {
|
|
|
1439
1604
|
const currentAgentKey = messageAgentKey(message);
|
|
1440
1605
|
return !incomingAgentKey || !currentAgentKey || currentAgentKey === incomingAgentKey;
|
|
1441
1606
|
};
|
|
1442
|
-
var canAttachToCurrentStreamingAssistant = (message) => {
|
|
1443
|
-
return !!message && message.role === "assistant" && message.isStreaming === true;
|
|
1444
|
-
};
|
|
1445
1607
|
|
|
1446
1608
|
// src/messageContract.ts
|
|
1447
1609
|
var isInternalMessageMetadata = (metadata) => metadata?.visibility === "internal";
|
|
@@ -1548,9 +1710,10 @@ var convertServerMessage = (msg, options = {}) => {
|
|
|
1548
1710
|
const isToolSender = msg.senderType === "tool";
|
|
1549
1711
|
const content = isToolSender ? "" : messageContent;
|
|
1550
1712
|
const reasoning = typeof msg.reasoning === "string" && msg.reasoning.length > 0 ? msg.reasoning : void 0;
|
|
1713
|
+
const llmAttemptId = typeof metadata?.[LLM_ATTEMPT_ID_METADATA_KEY] === "string" ? metadata[LLM_ATTEMPT_ID_METADATA_KEY].trim() : "";
|
|
1551
1714
|
const activityItems = [
|
|
1552
1715
|
...reasoning ? [{
|
|
1553
|
-
id: `${msg.id}:
|
|
1716
|
+
id: `${llmAttemptId || msg.id}:reasoning:0`,
|
|
1554
1717
|
kind: "thinking",
|
|
1555
1718
|
status: "complete",
|
|
1556
1719
|
completedAt: timestamp,
|
|
@@ -1631,38 +1794,6 @@ var prepareHydratedMessages = async (rawMessages, options = {}) => {
|
|
|
1631
1794
|
};
|
|
1632
1795
|
};
|
|
1633
1796
|
|
|
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 getLlmAttemptId = (event) => {
|
|
1642
|
-
if (!isRecord2(event)) return null;
|
|
1643
|
-
const metadata = isRecord2(event.metadata) ? event.metadata : {};
|
|
1644
|
-
if (typeof metadata.llmAttemptId === "string" && metadata.llmAttemptId.trim().length > 0) {
|
|
1645
|
-
return metadata.llmAttemptId.trim();
|
|
1646
|
-
}
|
|
1647
|
-
if (event.subjectType === "llm_attempt" && typeof event.subjectId === "string" && event.subjectId.trim().length > 0) {
|
|
1648
|
-
return event.subjectId.trim();
|
|
1649
|
-
}
|
|
1650
|
-
return null;
|
|
1651
|
-
};
|
|
1652
|
-
var isTerminalEmptyLlmResultEvent = (event) => {
|
|
1653
|
-
if (!isRecord2(event) || event.type !== "LLM_RESULT") return false;
|
|
1654
|
-
const payload = getStreamEventPayload(event);
|
|
1655
|
-
if (!isRecord2(payload)) return false;
|
|
1656
|
-
if (payload.answer !== "") return false;
|
|
1657
|
-
if (payload.finishReason === "tool_calls") return false;
|
|
1658
|
-
if (hasValues(payload.toolCalls)) return false;
|
|
1659
|
-
const metadata = isRecord2(event.metadata) ? event.metadata : {};
|
|
1660
|
-
const routing = isRecord2(metadata.routing) ? metadata.routing : {};
|
|
1661
|
-
if ((routing.action === "ask" || routing.action === "handoff") && typeof routing.targetId === "string" && routing.targetId.trim().length > 0) return false;
|
|
1662
|
-
if (hasValues(metadata.targetQueue)) return false;
|
|
1663
|
-
return true;
|
|
1664
|
-
};
|
|
1665
|
-
|
|
1666
1797
|
// src/threadIdentity.ts
|
|
1667
1798
|
var identityValues = ({ id, externalId }) => [id, externalId].filter((value) => typeof value === "string" && value.length > 0);
|
|
1668
1799
|
var isSameThreadIdentity = (owner, current) => {
|
|
@@ -1672,96 +1803,245 @@ var isSameThreadIdentity = (owner, current) => {
|
|
|
1672
1803
|
return ownerValues.some((value) => currentValues.has(value));
|
|
1673
1804
|
};
|
|
1674
1805
|
|
|
1675
|
-
// src/
|
|
1676
|
-
var
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
return JSON.stringify(value) ?? "";
|
|
1682
|
-
} catch {
|
|
1683
|
-
return String(value);
|
|
1684
|
-
}
|
|
1685
|
-
};
|
|
1686
|
-
var getMessageSignature = (message) => JSON.stringify({
|
|
1687
|
-
id: message.id,
|
|
1688
|
-
role: message.role,
|
|
1689
|
-
content: message.content ?? "",
|
|
1690
|
-
isStreaming: message.isStreaming === true,
|
|
1691
|
-
isComplete: message.isComplete === true,
|
|
1692
|
-
attachments: stringifyForCompare(message.attachments),
|
|
1693
|
-
activity: stringifyForCompare(message.activity),
|
|
1694
|
-
metadata: stringifyForCompare(message.metadata),
|
|
1695
|
-
sender: message.sender ? {
|
|
1696
|
-
id: message.sender.id,
|
|
1697
|
-
type: message.sender.type,
|
|
1698
|
-
name: message.sender.name,
|
|
1699
|
-
agentId: message.sender.agentId
|
|
1700
|
-
} : null
|
|
1806
|
+
// src/liveRun.ts
|
|
1807
|
+
var copyState = (state) => ({
|
|
1808
|
+
...state,
|
|
1809
|
+
attemptsById: new Map(state.attemptsById),
|
|
1810
|
+
toolMessageByCallId: new Map(state.toolMessageByCallId),
|
|
1811
|
+
pendingToolResultsByCallId: new Map(state.pendingToolResultsByCallId)
|
|
1701
1812
|
});
|
|
1702
|
-
var
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1813
|
+
var createLiveRunState = (initialMessageId) => ({
|
|
1814
|
+
initialMessageId,
|
|
1815
|
+
initialMessageClaimed: false,
|
|
1816
|
+
activeAttemptId: null,
|
|
1817
|
+
lastAttemptId: null,
|
|
1818
|
+
attemptsById: /* @__PURE__ */ new Map(),
|
|
1819
|
+
toolMessageByCallId: /* @__PURE__ */ new Map(),
|
|
1820
|
+
pendingToolResultsByCallId: /* @__PURE__ */ new Map()
|
|
1821
|
+
});
|
|
1822
|
+
var ensureAttempt = (state, attemptId, sender, at, options) => {
|
|
1823
|
+
const existing = state.attemptsById.get(attemptId);
|
|
1824
|
+
if (existing) {
|
|
1825
|
+
const next2 = copyState(state);
|
|
1826
|
+
const cursor2 = sender && existing.sender !== sender ? { ...existing, sender } : existing;
|
|
1827
|
+
next2.attemptsById.set(attemptId, cursor2);
|
|
1828
|
+
next2.activeAttemptId = attemptId;
|
|
1829
|
+
next2.lastAttemptId = attemptId;
|
|
1830
|
+
return { state: next2, cursor: cursor2, operations: [] };
|
|
1831
|
+
}
|
|
1832
|
+
const next = copyState(state);
|
|
1833
|
+
const messageId = next.initialMessageClaimed ? options.createId() : next.initialMessageId;
|
|
1834
|
+
const cursor = { messageId, sender };
|
|
1835
|
+
next.initialMessageClaimed = true;
|
|
1836
|
+
next.activeAttemptId = attemptId;
|
|
1837
|
+
next.lastAttemptId = attemptId;
|
|
1838
|
+
next.attemptsById.set(attemptId, cursor);
|
|
1839
|
+
return {
|
|
1840
|
+
state: next,
|
|
1841
|
+
cursor,
|
|
1842
|
+
operations: [{
|
|
1843
|
+
type: "ensure-attempt",
|
|
1844
|
+
messageId,
|
|
1845
|
+
attemptId,
|
|
1846
|
+
sender,
|
|
1847
|
+
at
|
|
1848
|
+
}]
|
|
1849
|
+
};
|
|
1719
1850
|
};
|
|
1720
|
-
var
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1851
|
+
var transitionLiveRun = (state, action, options) => {
|
|
1852
|
+
if (action.type === "attempt-start") {
|
|
1853
|
+
const ensured = ensureAttempt(
|
|
1854
|
+
state,
|
|
1855
|
+
action.attemptId,
|
|
1856
|
+
action.sender,
|
|
1857
|
+
action.at,
|
|
1858
|
+
options
|
|
1859
|
+
);
|
|
1860
|
+
return { state: ensured.state, operations: ensured.operations };
|
|
1861
|
+
}
|
|
1862
|
+
if (action.type === "token") {
|
|
1863
|
+
const ensured = ensureAttempt(
|
|
1864
|
+
state,
|
|
1865
|
+
action.attemptId,
|
|
1866
|
+
action.sender,
|
|
1867
|
+
action.at,
|
|
1868
|
+
options
|
|
1869
|
+
);
|
|
1870
|
+
return {
|
|
1871
|
+
state: ensured.state,
|
|
1872
|
+
operations: [
|
|
1873
|
+
...ensured.operations,
|
|
1874
|
+
{
|
|
1875
|
+
type: "update-token",
|
|
1876
|
+
messageId: ensured.cursor.messageId,
|
|
1877
|
+
phaseId: action.phaseId,
|
|
1878
|
+
partial: action.partial,
|
|
1879
|
+
isReasoning: action.isReasoning,
|
|
1880
|
+
sender: action.sender ?? ensured.cursor.sender,
|
|
1881
|
+
at: action.at
|
|
1882
|
+
}
|
|
1883
|
+
]
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
if (action.type === "attempt-result") {
|
|
1887
|
+
const resolvedAttemptId = state.attemptsById.has(action.attemptId) ? action.attemptId : state.activeAttemptId ?? state.lastAttemptId;
|
|
1888
|
+
const cursor = resolvedAttemptId ? state.attemptsById.get(resolvedAttemptId) : void 0;
|
|
1889
|
+
if (!cursor) return { state, operations: [] };
|
|
1890
|
+
const next = copyState(state);
|
|
1891
|
+
if (next.activeAttemptId === resolvedAttemptId) {
|
|
1892
|
+
next.activeAttemptId = null;
|
|
1728
1893
|
}
|
|
1894
|
+
next.lastAttemptId = resolvedAttemptId;
|
|
1895
|
+
return {
|
|
1896
|
+
state: next,
|
|
1897
|
+
operations: [{
|
|
1898
|
+
type: "finalize-attempt",
|
|
1899
|
+
messageId: cursor.messageId,
|
|
1900
|
+
answer: action.answer,
|
|
1901
|
+
at: action.at
|
|
1902
|
+
}]
|
|
1903
|
+
};
|
|
1729
1904
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1905
|
+
if (action.type === "tool-call") {
|
|
1906
|
+
if (state.toolMessageByCallId.has(action.toolCall.id)) {
|
|
1907
|
+
return { state, operations: [] };
|
|
1908
|
+
}
|
|
1909
|
+
const attemptId = action.attemptId ?? state.activeAttemptId ?? state.lastAttemptId ?? `tool-attempt:${action.toolCall.id}`;
|
|
1910
|
+
const ensured = ensureAttempt(
|
|
1911
|
+
state,
|
|
1912
|
+
attemptId,
|
|
1913
|
+
action.sender,
|
|
1914
|
+
action.at,
|
|
1915
|
+
options
|
|
1916
|
+
);
|
|
1917
|
+
const next = copyState(ensured.state);
|
|
1918
|
+
next.toolMessageByCallId.set(action.toolCall.id, ensured.cursor.messageId);
|
|
1919
|
+
const pendingResult = next.pendingToolResultsByCallId.get(action.toolCall.id);
|
|
1920
|
+
if (pendingResult) {
|
|
1921
|
+
next.pendingToolResultsByCallId.delete(action.toolCall.id);
|
|
1922
|
+
}
|
|
1923
|
+
return {
|
|
1924
|
+
state: next,
|
|
1925
|
+
operations: [
|
|
1926
|
+
...ensured.operations,
|
|
1927
|
+
{
|
|
1928
|
+
type: "append-tool",
|
|
1929
|
+
messageId: ensured.cursor.messageId,
|
|
1930
|
+
toolCall: action.toolCall,
|
|
1931
|
+
sender: action.sender ?? ensured.cursor.sender,
|
|
1932
|
+
at: action.at
|
|
1933
|
+
},
|
|
1934
|
+
...pendingResult ? [{
|
|
1935
|
+
type: "resolve-tool",
|
|
1936
|
+
messageId: ensured.cursor.messageId,
|
|
1937
|
+
update: pendingResult
|
|
1938
|
+
}] : []
|
|
1939
|
+
]
|
|
1940
|
+
};
|
|
1735
1941
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1942
|
+
const messageId = action.update.id ? state.toolMessageByCallId.get(action.update.id) : void 0;
|
|
1943
|
+
if (!messageId || !action.update.id) {
|
|
1944
|
+
if (!action.update.id) return { state, operations: [] };
|
|
1945
|
+
const next = copyState(state);
|
|
1946
|
+
next.pendingToolResultsByCallId.set(action.update.id, action.update);
|
|
1947
|
+
return { state: next, operations: [] };
|
|
1738
1948
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
if (
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1949
|
+
return {
|
|
1950
|
+
state,
|
|
1951
|
+
operations: [{ type: "resolve-tool", messageId, update: action.update }]
|
|
1952
|
+
};
|
|
1953
|
+
};
|
|
1954
|
+
var applyOperation = (messages, operation) => {
|
|
1955
|
+
const index = messages.findIndex((message) => message.id === operation.messageId);
|
|
1956
|
+
if (operation.type === "ensure-attempt") {
|
|
1957
|
+
if (index >= 0) {
|
|
1958
|
+
const current2 = messages[index];
|
|
1959
|
+
const nextMessage = {
|
|
1960
|
+
...current2,
|
|
1961
|
+
metadata: {
|
|
1962
|
+
...current2.metadata ?? {},
|
|
1963
|
+
[LLM_ATTEMPT_ID_METADATA_KEY]: operation.attemptId
|
|
1964
|
+
},
|
|
1965
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
1966
|
+
};
|
|
1967
|
+
if (current2.metadata?.[LLM_ATTEMPT_ID_METADATA_KEY] === operation.attemptId && (!operation.sender || current2.sender === operation.sender)) return messages;
|
|
1968
|
+
const next2 = [...messages];
|
|
1969
|
+
next2[index] = nextMessage;
|
|
1970
|
+
return next2;
|
|
1751
1971
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1972
|
+
return [
|
|
1973
|
+
...messages,
|
|
1974
|
+
{
|
|
1975
|
+
id: operation.messageId,
|
|
1976
|
+
role: "assistant",
|
|
1977
|
+
content: "",
|
|
1978
|
+
timestamp: operation.at,
|
|
1979
|
+
isStreaming: true,
|
|
1980
|
+
isComplete: false,
|
|
1981
|
+
metadata: { [LLM_ATTEMPT_ID_METADATA_KEY]: operation.attemptId },
|
|
1982
|
+
activity: {
|
|
1983
|
+
items: [{
|
|
1984
|
+
id: `${operation.messageId}:pending`,
|
|
1985
|
+
kind: "answering",
|
|
1986
|
+
status: "active",
|
|
1987
|
+
startedAt: operation.at
|
|
1988
|
+
}]
|
|
1989
|
+
},
|
|
1990
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
1991
|
+
}
|
|
1992
|
+
];
|
|
1993
|
+
}
|
|
1994
|
+
if (index < 0) return messages;
|
|
1995
|
+
const current = messages[index];
|
|
1996
|
+
let updated;
|
|
1997
|
+
if (operation.type === "update-token") {
|
|
1998
|
+
updated = {
|
|
1999
|
+
...updateAssistantMessageToken(current, {
|
|
2000
|
+
partial: operation.partial,
|
|
2001
|
+
isReasoning: operation.isReasoning,
|
|
2002
|
+
activityId: operation.phaseId,
|
|
2003
|
+
at: operation.at
|
|
2004
|
+
}),
|
|
2005
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
2006
|
+
};
|
|
2007
|
+
} else if (operation.type === "finalize-attempt") {
|
|
2008
|
+
updated = finalizeAssistantMessage(current, operation.answer, operation.at);
|
|
2009
|
+
} else if (operation.type === "append-tool") {
|
|
2010
|
+
updated = {
|
|
2011
|
+
...appendAssistantToolCall(current, {
|
|
2012
|
+
...operation.toolCall,
|
|
2013
|
+
startTime: operation.toolCall.startTime ?? operation.at
|
|
2014
|
+
}),
|
|
2015
|
+
...operation.sender ? { sender: operation.sender } : {}
|
|
2016
|
+
};
|
|
2017
|
+
} else {
|
|
2018
|
+
const toolItem = current.activity?.items.find((item) => item.kind === "tool" && item.id === operation.update.id);
|
|
2019
|
+
if (!toolItem) return messages;
|
|
2020
|
+
updated = applyAssistantToolResult(current, {
|
|
2021
|
+
...operation.update.id ? { id: operation.update.id } : {},
|
|
2022
|
+
...operation.update.toolExecutionId ? { toolExecutionId: operation.update.toolExecutionId } : {},
|
|
2023
|
+
name: operation.update.name ?? toolItem.toolName ?? toolItem.id,
|
|
2024
|
+
status: operation.update.status,
|
|
2025
|
+
...operation.update.result !== void 0 ? { result: operation.update.result } : {},
|
|
2026
|
+
...operation.update.error !== void 0 ? { error: operation.update.error } : {},
|
|
2027
|
+
endTime: operation.update.endTime
|
|
1762
2028
|
});
|
|
2029
|
+
const hasActiveTool = updated.activity?.items.some((item) => item.kind === "tool" && item.status === "active") ?? false;
|
|
2030
|
+
updated = {
|
|
2031
|
+
...updated,
|
|
2032
|
+
isStreaming: hasActiveTool,
|
|
2033
|
+
isComplete: !hasActiveTool
|
|
2034
|
+
};
|
|
1763
2035
|
}
|
|
1764
|
-
|
|
2036
|
+
if (updated === current) return messages;
|
|
2037
|
+
const next = [...messages];
|
|
2038
|
+
next[index] = updated;
|
|
2039
|
+
return next;
|
|
2040
|
+
};
|
|
2041
|
+
var applyLiveRunOperations = (messages, operations) => operations.reduce(applyOperation, messages);
|
|
2042
|
+
var getLatestLiveRunMessageId = (state) => {
|
|
2043
|
+
const attemptId = state.activeAttemptId ?? state.lastAttemptId;
|
|
2044
|
+
return attemptId ? state.attemptsById.get(attemptId)?.messageId ?? state.initialMessageId : state.initialMessageId;
|
|
1765
2045
|
};
|
|
1766
2046
|
|
|
1767
2047
|
// src/useCopilotzChat.ts
|
|
@@ -1770,6 +2050,10 @@ var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${
|
|
|
1770
2050
|
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
1771
2051
|
var getEventPayload = (event) => getStreamEventPayload(event);
|
|
1772
2052
|
var getEventSenderType = (payload) => payload?.senderType || payload?.sender?.type;
|
|
2053
|
+
var getEventTimestamp = (event) => {
|
|
2054
|
+
const timestamp = typeof event?.createdAt === "string" ? new Date(event.createdAt).getTime() : NaN;
|
|
2055
|
+
return Number.isFinite(timestamp) ? timestamp : nowTs();
|
|
2056
|
+
};
|
|
1773
2057
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1774
2058
|
var normalizeThreadTag = (value) => {
|
|
1775
2059
|
if (!isRecord3(value)) return null;
|
|
@@ -1798,11 +2082,11 @@ var createEmptyMessagePageInfo = () => ({
|
|
|
1798
2082
|
oldestMessageId: null,
|
|
1799
2083
|
newestMessageId: null
|
|
1800
2084
|
});
|
|
1801
|
-
var createPendingAssistantActivity = () => ({
|
|
2085
|
+
var createPendingAssistantActivity = (messageId) => ({
|
|
1802
2086
|
items: [
|
|
1803
2087
|
{
|
|
1804
|
-
id:
|
|
1805
|
-
kind: "
|
|
2088
|
+
id: `${messageId}:pending`,
|
|
2089
|
+
kind: "answering",
|
|
1806
2090
|
status: "active",
|
|
1807
2091
|
startedAt: nowTs()
|
|
1808
2092
|
}
|
|
@@ -1845,7 +2129,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
1845
2129
|
assistantName
|
|
1846
2130
|
});
|
|
1847
2131
|
const persistedToolUpdatesRef = useRef2([]);
|
|
1848
|
-
const liveToolUpdatesRef = useRef2([]);
|
|
1849
2132
|
const stopRequestedRef = useRef2(false);
|
|
1850
2133
|
const recoveryPollGenerationRef = useRef2(0);
|
|
1851
2134
|
threadsRef.current = threads;
|
|
@@ -2041,7 +2324,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2041
2324
|
setIsLoadingOlderMessages(false);
|
|
2042
2325
|
setMessagePageInfo(createEmptyMessagePageInfo());
|
|
2043
2326
|
persistedToolUpdatesRef.current = [];
|
|
2044
|
-
liveToolUpdatesRef.current = [];
|
|
2045
2327
|
try {
|
|
2046
2328
|
const page = await fetchThreadMessagesPage(threadId, { limit: THREAD_MESSAGES_PAGE_SIZE }, getRequestHeaders);
|
|
2047
2329
|
const { viewMessages, toolResultUpdates } = await prepareThreadMessages(page.data);
|
|
@@ -2401,161 +2683,26 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2401
2683
|
}, []);
|
|
2402
2684
|
const sendCopilotzMessage = useCallback2(
|
|
2403
2685
|
async (params) => {
|
|
2404
|
-
|
|
2405
|
-
let
|
|
2686
|
+
const initialAssistantMessageId = params.assistantMessageId ?? generateId();
|
|
2687
|
+
let liveRunState = createLiveRunState(initialAssistantMessageId);
|
|
2406
2688
|
const streamOwnerThreadId = currentThreadIdRef.current;
|
|
2407
2689
|
const streamOwnerThreadExternalId = params.threadExternalId ?? currentThreadExternalIdRef.current;
|
|
2408
2690
|
const streamStillOwnsVisibleThread = () => isSameThreadIdentity(
|
|
2409
2691
|
{ id: streamOwnerThreadId, externalId: streamOwnerThreadExternalId },
|
|
2410
2692
|
{ id: currentThreadIdRef.current, externalId: currentThreadExternalIdRef.current }
|
|
2411
2693
|
);
|
|
2412
|
-
params.onBeforeStart?.(
|
|
2694
|
+
params.onBeforeStart?.(initialAssistantMessageId);
|
|
2413
2695
|
let hasStreamProgress = false;
|
|
2414
|
-
const
|
|
2696
|
+
const dispatchLiveRunAction = (action) => {
|
|
2415
2697
|
if (!streamStillOwnsVisibleThread()) return;
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
setIsRecoveringStream(false);
|
|
2419
|
-
}
|
|
2420
|
-
const isReasoning = opts?.isReasoning ?? false;
|
|
2421
|
-
const nextSender = opts?.agent ? resolveAgentSender(opts.agent, senderOptionsRef.current) : currentAssistantSender;
|
|
2422
|
-
if (nextSender) {
|
|
2423
|
-
currentAssistantSender = nextSender;
|
|
2424
|
-
}
|
|
2425
|
-
const nextAgentKey = currentAssistantSender?.agentId ?? currentAssistantSender?.id ?? null;
|
|
2426
|
-
const applyUpdate = (msg) => {
|
|
2427
|
-
return {
|
|
2428
|
-
...updateAssistantMessageToken(msg, {
|
|
2429
|
-
partial,
|
|
2430
|
-
isReasoning
|
|
2431
|
-
}),
|
|
2432
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2433
|
-
};
|
|
2434
|
-
};
|
|
2435
|
-
setMessages((prev) => {
|
|
2436
|
-
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
2437
|
-
if (idx >= 0 && canAttachToCurrentStreamingAssistant(prev[idx])) {
|
|
2438
|
-
const msg = prev[idx];
|
|
2439
|
-
const next = applyUpdate(msg);
|
|
2440
|
-
if (msg.content === next.content && msg.activity === next.activity && msg.isStreaming === next.isStreaming && msg.isComplete === next.isComplete) {
|
|
2441
|
-
return prev;
|
|
2442
|
-
}
|
|
2443
|
-
const updated = [...prev];
|
|
2444
|
-
updated[idx] = next;
|
|
2445
|
-
return updated;
|
|
2446
|
-
}
|
|
2447
|
-
const last = prev[prev.length - 1];
|
|
2448
|
-
if (canAttachToStreamingAssistant(last, nextAgentKey)) {
|
|
2449
|
-
currentAssistantId = last.id;
|
|
2450
|
-
const next = applyUpdate(last);
|
|
2451
|
-
if (last.content === next.content && last.activity === next.activity && last.isStreaming === next.isStreaming && last.isComplete === next.isComplete) {
|
|
2452
|
-
return prev;
|
|
2453
|
-
}
|
|
2454
|
-
const updated = [...prev];
|
|
2455
|
-
updated[prev.length - 1] = next;
|
|
2456
|
-
return updated;
|
|
2457
|
-
}
|
|
2458
|
-
const lastStreamingBelongsToDifferentAgent = Boolean(nextAgentKey) && last?.role === "assistant" && last.isStreaming && Boolean(messageAgentKey(last)) && messageAgentKey(last) !== nextAgentKey;
|
|
2459
|
-
if (!prev.length || prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming || lastStreamingBelongsToDifferentAgent) {
|
|
2460
|
-
const newId = generateId();
|
|
2461
|
-
currentAssistantId = newId;
|
|
2462
|
-
const base = {
|
|
2463
|
-
id: newId,
|
|
2464
|
-
role: "assistant",
|
|
2465
|
-
content: "",
|
|
2466
|
-
timestamp: nowTs(),
|
|
2467
|
-
isStreaming: true,
|
|
2468
|
-
isComplete: false,
|
|
2469
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2470
|
-
};
|
|
2471
|
-
return [...prev, applyUpdate(base)];
|
|
2472
|
-
}
|
|
2473
|
-
return prev;
|
|
2474
|
-
});
|
|
2475
|
-
};
|
|
2476
|
-
const finalizeCurrentAssistantBubble = () => {
|
|
2477
|
-
if (!streamStillOwnsVisibleThread()) return;
|
|
2478
|
-
setMessages((prev) => {
|
|
2479
|
-
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
2480
|
-
if (idx < 0) return prev;
|
|
2481
|
-
const msg = prev[idx];
|
|
2482
|
-
if (!msg.isStreaming && msg.isComplete) return prev;
|
|
2483
|
-
const updated = [...prev];
|
|
2484
|
-
updated[idx] = closeAssistantMessage(msg);
|
|
2485
|
-
return updated;
|
|
2698
|
+
const transition = transitionLiveRun(liveRunState, action, {
|
|
2699
|
+
createId: generateId
|
|
2486
2700
|
});
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
if (!streamStillOwnsVisibleThread()) return;
|
|
2491
|
-
let matched = false;
|
|
2492
|
-
setMessages((prev) => {
|
|
2493
|
-
const next = applyToolResultUpdateToMessages(prev, update, {
|
|
2494
|
-
isStreaming: true,
|
|
2495
|
-
isComplete: false
|
|
2496
|
-
});
|
|
2497
|
-
matched = next.matched;
|
|
2498
|
-
return next.matched ? next.messages : prev;
|
|
2499
|
-
});
|
|
2500
|
-
if (!matched) {
|
|
2501
|
-
liveToolUpdatesRef.current.push(update);
|
|
2701
|
+
liveRunState = transition.state;
|
|
2702
|
+
if (transition.operations.length > 0) {
|
|
2703
|
+
setMessages((prev) => applyLiveRunOperations(prev, transition.operations));
|
|
2502
2704
|
}
|
|
2503
2705
|
};
|
|
2504
|
-
const finalizeActiveAssistantTurn = (finalAnswer, llmAttemptId) => {
|
|
2505
|
-
if (!streamStillOwnsVisibleThread()) return;
|
|
2506
|
-
setMessages((prev) => {
|
|
2507
|
-
const currentIdx = prev.findIndex((message2) => message2.id === currentAssistantId && message2.role === "assistant");
|
|
2508
|
-
const fallbackIdx = currentIdx >= 0 ? currentIdx : (() => {
|
|
2509
|
-
for (let i = prev.length - 1; i >= 0; i--) {
|
|
2510
|
-
if (prev[i].role === "assistant" && prev[i].isStreaming) {
|
|
2511
|
-
return i;
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
return -1;
|
|
2515
|
-
})();
|
|
2516
|
-
if (fallbackIdx < 0) return prev;
|
|
2517
|
-
const message = prev[fallbackIdx];
|
|
2518
|
-
const correlatedMessage = llmAttemptId ? {
|
|
2519
|
-
...message,
|
|
2520
|
-
metadata: {
|
|
2521
|
-
...message.metadata ?? {},
|
|
2522
|
-
[LLM_ATTEMPT_ID_METADATA_KEY]: llmAttemptId
|
|
2523
|
-
}
|
|
2524
|
-
} : message;
|
|
2525
|
-
const nextMessage = finalizeAssistantMessage(correlatedMessage, finalAnswer);
|
|
2526
|
-
if (message.content === nextMessage.content && message.isStreaming === nextMessage.isStreaming && message.isComplete === nextMessage.isComplete && message.activity === nextMessage.activity && message.metadata === nextMessage.metadata) {
|
|
2527
|
-
return prev;
|
|
2528
|
-
}
|
|
2529
|
-
const updated = [...prev];
|
|
2530
|
-
updated[fallbackIdx] = nextMessage;
|
|
2531
|
-
currentAssistantId = nextMessage.id;
|
|
2532
|
-
return updated;
|
|
2533
|
-
});
|
|
2534
|
-
};
|
|
2535
|
-
const toServerMessageFromEvent = async (event) => {
|
|
2536
|
-
if (!event) return null;
|
|
2537
|
-
const type = event?.type || "";
|
|
2538
|
-
const payload = event?.payload ?? event;
|
|
2539
|
-
if (type === "TOOL_CALL") {
|
|
2540
|
-
const parsedToolCall = extractLiveToolCall(payload);
|
|
2541
|
-
return {
|
|
2542
|
-
id: generateId(),
|
|
2543
|
-
threadId: curThreadId ?? "",
|
|
2544
|
-
senderType: "tool",
|
|
2545
|
-
content: "",
|
|
2546
|
-
toolCalls: [
|
|
2547
|
-
{
|
|
2548
|
-
id: parsedToolCall.id ?? generateId(),
|
|
2549
|
-
name: parsedToolCall.name,
|
|
2550
|
-
args: parsedToolCall.arguments,
|
|
2551
|
-
...parsedToolCall.result !== void 0 ? { output: parsedToolCall.result } : {},
|
|
2552
|
-
status: parsedToolCall.status
|
|
2553
|
-
}
|
|
2554
|
-
]
|
|
2555
|
-
};
|
|
2556
|
-
}
|
|
2557
|
-
return null;
|
|
2558
|
-
};
|
|
2559
2706
|
const abortController = new AbortController();
|
|
2560
2707
|
if (!params.preserveActiveRun) {
|
|
2561
2708
|
abortControllerRef.current?.abort();
|
|
@@ -2566,7 +2713,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2566
2713
|
stopRequestedRef.current = false;
|
|
2567
2714
|
setThreadActivityStatus("running");
|
|
2568
2715
|
setIsStreaming(true);
|
|
2569
|
-
liveToolUpdatesRef.current = [];
|
|
2570
2716
|
let streamError = null;
|
|
2571
2717
|
const resolveActivityThreadId = async () => {
|
|
2572
2718
|
if (params.threadId) return params.threadId;
|
|
@@ -2613,10 +2759,26 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2613
2759
|
participants: participantsRef.current,
|
|
2614
2760
|
targetAgent: targetAgentNameRef.current,
|
|
2615
2761
|
getRequestHeaders,
|
|
2616
|
-
onToken: (token, _isComplete, raw, opts) =>
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2762
|
+
onToken: (token, _isComplete, raw, opts) => {
|
|
2763
|
+
const rawAgent = raw?.payload?.agent ?? raw?.agent ?? null;
|
|
2764
|
+
const sender = rawAgent ? resolveAgentSender(rawAgent, senderOptionsRef.current) : params.assistantSender;
|
|
2765
|
+
const attemptId = opts?.llmAttemptId ?? liveRunState.activeAttemptId ?? liveRunState.lastAttemptId ?? `stream-attempt:${initialAssistantMessageId}`;
|
|
2766
|
+
const isReasoning = opts?.isReasoning ?? false;
|
|
2767
|
+
const phaseId = opts?.phaseId ?? `${attemptId}:${isReasoning ? "reasoning" : "answer"}:0`;
|
|
2768
|
+
if (token.length > 0) {
|
|
2769
|
+
hasStreamProgress = true;
|
|
2770
|
+
setIsRecoveringStream(false);
|
|
2771
|
+
}
|
|
2772
|
+
dispatchLiveRunAction({
|
|
2773
|
+
type: "token",
|
|
2774
|
+
attemptId,
|
|
2775
|
+
phaseId,
|
|
2776
|
+
partial: token,
|
|
2777
|
+
isReasoning,
|
|
2778
|
+
sender,
|
|
2779
|
+
at: getEventTimestamp(raw)
|
|
2780
|
+
});
|
|
2781
|
+
},
|
|
2620
2782
|
onMessageEvent: async (event) => {
|
|
2621
2783
|
if (!streamStillOwnsVisibleThread()) return;
|
|
2622
2784
|
const intercepted = applyEventInterceptor(event);
|
|
@@ -2625,14 +2787,37 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2625
2787
|
}
|
|
2626
2788
|
const type = event?.type || "";
|
|
2627
2789
|
const payload = getEventPayload(event);
|
|
2790
|
+
if (type === "LLM_CALL") {
|
|
2791
|
+
const attemptId = getLlmAttemptId(event);
|
|
2792
|
+
if (attemptId) {
|
|
2793
|
+
dispatchLiveRunAction({
|
|
2794
|
+
type: "attempt-start",
|
|
2795
|
+
attemptId,
|
|
2796
|
+
sender: resolveLiveEventSender(event, senderOptionsRef.current),
|
|
2797
|
+
at: getEventTimestamp(event)
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2628
2802
|
if (type === "TOOL_RESULT") {
|
|
2629
2803
|
processToolOutput(payload ?? {});
|
|
2630
|
-
|
|
2804
|
+
dispatchLiveRunAction({
|
|
2805
|
+
type: "tool-result",
|
|
2806
|
+
update: extractLiveToolResultUpdate(payload ?? {})
|
|
2807
|
+
});
|
|
2631
2808
|
return;
|
|
2632
2809
|
}
|
|
2633
2810
|
if (type === "LLM_RESULT") {
|
|
2634
2811
|
const finalAnswer = typeof payload?.answer === "string" ? payload.answer : void 0;
|
|
2635
|
-
|
|
2812
|
+
const attemptId = getLlmAttemptId(event) ?? liveRunState.activeAttemptId ?? liveRunState.lastAttemptId;
|
|
2813
|
+
if (attemptId) {
|
|
2814
|
+
dispatchLiveRunAction({
|
|
2815
|
+
type: "attempt-result",
|
|
2816
|
+
attemptId,
|
|
2817
|
+
answer: finalAnswer,
|
|
2818
|
+
at: getEventTimestamp(event)
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2636
2821
|
if (isTerminalEmptyLlmResultEvent(event)) {
|
|
2637
2822
|
finalizeStreamingPlaceholders();
|
|
2638
2823
|
}
|
|
@@ -2644,87 +2829,22 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2644
2829
|
if (type === "TOOL_CALL") {
|
|
2645
2830
|
const parsedToolCall = extractLiveToolCall(payload ?? {});
|
|
2646
2831
|
const eventSender = resolveLiveEventSender(event, senderOptionsRef.current);
|
|
2647
|
-
currentAssistantSender = eventSender;
|
|
2648
|
-
const eventAgentKey = currentAssistantSender.agentId ?? currentAssistantSender.id;
|
|
2649
2832
|
const callId = parsedToolCall.id ?? generateId();
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
if (!message) return false;
|
|
2664
|
-
return message.role === "assistant" && message.isStreaming && message.content.trim().length === 0 && !message.attachments?.length;
|
|
2665
|
-
};
|
|
2666
|
-
const appendToolCall = (msg) => ({
|
|
2667
|
-
...appendAssistantToolCall(msg, {
|
|
2668
|
-
id: callId,
|
|
2669
|
-
name: toolName,
|
|
2670
|
-
arguments: parsedToolCall.arguments,
|
|
2671
|
-
...initialResult !== void 0 ? { result: initialResult } : {},
|
|
2672
|
-
status: initialStatus,
|
|
2673
|
-
startTime: Date.now(),
|
|
2674
|
-
...endTime !== void 0 ? { endTime } : {}
|
|
2675
|
-
})
|
|
2676
|
-
});
|
|
2677
|
-
const currentIdx = prev.findIndex((message) => message.id === currentAssistantId && message.role === "assistant" && message.isStreaming && canHostActivity(message));
|
|
2678
|
-
if (currentIdx >= 0) {
|
|
2679
|
-
const next = [...prev];
|
|
2680
|
-
next[currentIdx] = appendToolCall({
|
|
2681
|
-
...next[currentIdx],
|
|
2682
|
-
isStreaming: true,
|
|
2683
|
-
isComplete: false,
|
|
2684
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2685
|
-
});
|
|
2686
|
-
return next;
|
|
2687
|
-
}
|
|
2688
|
-
const last = prev[prev.length - 1];
|
|
2689
|
-
if (canHostActivity(last) && canAttachToStreamingAssistant(last, eventAgentKey)) {
|
|
2690
|
-
currentAssistantId = last.id;
|
|
2691
|
-
const next = [...prev];
|
|
2692
|
-
next[prev.length - 1] = appendToolCall({
|
|
2693
|
-
...last,
|
|
2694
|
-
isStreaming: true,
|
|
2695
|
-
isComplete: false,
|
|
2696
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2697
|
-
});
|
|
2698
|
-
return next;
|
|
2699
|
-
}
|
|
2700
|
-
const newId = generateId();
|
|
2701
|
-
currentAssistantId = newId;
|
|
2702
|
-
return [
|
|
2703
|
-
...prev,
|
|
2704
|
-
appendToolCall({
|
|
2705
|
-
id: newId,
|
|
2706
|
-
role: "assistant",
|
|
2707
|
-
content: "",
|
|
2708
|
-
timestamp: nowTs(),
|
|
2709
|
-
isStreaming: true,
|
|
2710
|
-
isComplete: false,
|
|
2711
|
-
...currentAssistantSender ? { sender: currentAssistantSender } : {}
|
|
2712
|
-
})
|
|
2713
|
-
];
|
|
2714
|
-
})()
|
|
2715
|
-
);
|
|
2716
|
-
hasStreamProgress = true;
|
|
2717
|
-
return;
|
|
2718
|
-
}
|
|
2719
|
-
const sm = await toServerMessageFromEvent(event);
|
|
2720
|
-
if (sm) {
|
|
2721
|
-
const viewMsg = convertServerMessage(sm, {
|
|
2722
|
-
senderOptions: senderOptionsRef.current,
|
|
2723
|
-
createId: generateId,
|
|
2724
|
-
now: nowTs
|
|
2833
|
+
dispatchLiveRunAction({
|
|
2834
|
+
type: "tool-call",
|
|
2835
|
+
attemptId: getLlmAttemptId(event) ?? liveRunState.activeAttemptId ?? liveRunState.lastAttemptId,
|
|
2836
|
+
sender: eventSender,
|
|
2837
|
+
at: getEventTimestamp(event),
|
|
2838
|
+
toolCall: {
|
|
2839
|
+
id: callId,
|
|
2840
|
+
name: parsedToolCall.name,
|
|
2841
|
+
arguments: parsedToolCall.arguments,
|
|
2842
|
+
status: parsedToolCall.status,
|
|
2843
|
+
...parsedToolCall.toolExecutionId ? { toolExecutionId: parsedToolCall.toolExecutionId } : {},
|
|
2844
|
+
...parsedToolCall.result !== void 0 ? { result: parsedToolCall.result } : {}
|
|
2845
|
+
}
|
|
2725
2846
|
});
|
|
2726
|
-
|
|
2727
|
-
setMessages((prev) => [...prev, viewMsg]);
|
|
2847
|
+
hasStreamProgress = true;
|
|
2728
2848
|
return;
|
|
2729
2849
|
}
|
|
2730
2850
|
handleStreamMessageEvent(event);
|
|
@@ -2738,10 +2858,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2738
2858
|
if (intercepted?.handled) {
|
|
2739
2859
|
return;
|
|
2740
2860
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
handleStreamAssetEvent(payload, currentAssistantId);
|
|
2744
|
-
})();
|
|
2861
|
+
if (!hasStreamProgress) return;
|
|
2862
|
+
handleStreamAssetEvent(payload, getLatestLiveRunMessageId(liveRunState));
|
|
2745
2863
|
},
|
|
2746
2864
|
signal: abortController.signal
|
|
2747
2865
|
});
|
|
@@ -2774,16 +2892,16 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2774
2892
|
abortControllerRef.current = null;
|
|
2775
2893
|
}
|
|
2776
2894
|
if (!streamStillOwnsVisibleThread()) {
|
|
2777
|
-
return
|
|
2895
|
+
return getLatestLiveRunMessageId(liveRunState);
|
|
2778
2896
|
}
|
|
2779
2897
|
if (recoveryStarted) {
|
|
2780
|
-
return
|
|
2898
|
+
return getLatestLiveRunMessageId(liveRunState);
|
|
2781
2899
|
}
|
|
2782
2900
|
finalizeStreamingPlaceholders();
|
|
2783
2901
|
if (streamError) {
|
|
2784
2902
|
throw streamError;
|
|
2785
2903
|
}
|
|
2786
|
-
return
|
|
2904
|
+
return getLatestLiveRunMessageId(liveRunState);
|
|
2787
2905
|
},
|
|
2788
2906
|
[applyEventInterceptor, handleStreamMessageEvent, handleStreamAssetEvent, fetchAndSetThreadsState, finalizeStreamingPlaceholders, getRequestHeaders, refreshThreadMessages, startThreadActivityRecovery]
|
|
2789
2907
|
);
|
|
@@ -2840,15 +2958,16 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2840
2958
|
},
|
|
2841
2959
|
senderOptionsRef.current
|
|
2842
2960
|
) : preferredAgentRef.current ? resolveAgentSender({ id: preferredAgentRef.current, name: preferredAgentRef.current }, senderOptionsRef.current) : resolveAssistantFallbackSender(senderOptionsRef.current);
|
|
2961
|
+
const assistantMessageId = generateId();
|
|
2843
2962
|
const assistantPlaceholder = {
|
|
2844
|
-
id:
|
|
2963
|
+
id: assistantMessageId,
|
|
2845
2964
|
role: "assistant",
|
|
2846
2965
|
content: "",
|
|
2847
2966
|
timestamp: timestamp + 1,
|
|
2848
2967
|
isStreaming: true,
|
|
2849
2968
|
isComplete: false,
|
|
2850
2969
|
sender: assistantSender,
|
|
2851
|
-
activity: createPendingAssistantActivity()
|
|
2970
|
+
activity: createPendingAssistantActivity(assistantMessageId)
|
|
2852
2971
|
};
|
|
2853
2972
|
setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);
|
|
2854
2973
|
setSpecialState(null);
|
|
@@ -2956,7 +3075,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
|
|
|
2956
3075
|
isStreaming: true,
|
|
2957
3076
|
isComplete: false,
|
|
2958
3077
|
sender: assistantSender,
|
|
2959
|
-
activity: createPendingAssistantActivity()
|
|
3078
|
+
activity: createPendingAssistantActivity(assistantMessageId)
|
|
2960
3079
|
}
|
|
2961
3080
|
]);
|
|
2962
3081
|
setMessagePageInfo(createEmptyMessagePageInfo());
|