dooers-agents-client 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +250 -39
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.cts +182 -2
- package/dist/main.d.ts +182 -2
- package/dist/main.js +251 -41
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -246,6 +246,8 @@ function toWireContentPart(p) {
|
|
|
246
246
|
ref_id: p.refId
|
|
247
247
|
};
|
|
248
248
|
if (p.duration != null) w.duration = p.duration;
|
|
249
|
+
if (p.filename?.trim()) w.filename = p.filename.trim();
|
|
250
|
+
if (p.mimeType?.trim()) w.mime_type = p.mimeType.trim();
|
|
249
251
|
if (p.url) w.url = p.url;
|
|
250
252
|
return w;
|
|
251
253
|
}
|
|
@@ -254,6 +256,8 @@ function toWireContentPart(p) {
|
|
|
254
256
|
type: "image",
|
|
255
257
|
ref_id: p.refId
|
|
256
258
|
};
|
|
259
|
+
if (p.filename?.trim()) w.filename = p.filename.trim();
|
|
260
|
+
if (p.mimeType?.trim()) w.mime_type = p.mimeType.trim();
|
|
257
261
|
if (p.url) w.url = p.url;
|
|
258
262
|
return w;
|
|
259
263
|
}
|
|
@@ -262,6 +266,8 @@ function toWireContentPart(p) {
|
|
|
262
266
|
type: "document",
|
|
263
267
|
ref_id: p.refId
|
|
264
268
|
};
|
|
269
|
+
if (p.filename?.trim()) w.filename = p.filename.trim();
|
|
270
|
+
if (p.mimeType?.trim()) w.mime_type = p.mimeType.trim();
|
|
265
271
|
if (p.url) w.url = p.url;
|
|
266
272
|
return w;
|
|
267
273
|
}
|
|
@@ -272,6 +278,35 @@ function toWireContentPart(p) {
|
|
|
272
278
|
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
273
279
|
var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3];
|
|
274
280
|
var SEND_MESSAGE_TIMEOUT = 3e4;
|
|
281
|
+
function blobPartFilename(blob, override) {
|
|
282
|
+
const o = (override ?? "").trim();
|
|
283
|
+
if (o) return o;
|
|
284
|
+
const t = (blob.type || "").toLowerCase();
|
|
285
|
+
if (t.includes("webm")) return "recording.webm";
|
|
286
|
+
if (t.includes("ogg")) return "recording.ogg";
|
|
287
|
+
if (t.includes("mpeg") || t.includes("mp3")) return "recording.mp3";
|
|
288
|
+
if (t.includes("mp4") || t.includes("m4a") || t.includes("aac")) return "recording.m4a";
|
|
289
|
+
if (t.startsWith("audio/")) return `recording.${t.split("/")[1]?.split("+")[0] || "bin"}`;
|
|
290
|
+
return "recording.bin";
|
|
291
|
+
}
|
|
292
|
+
async function readHttpErrorMessage(response) {
|
|
293
|
+
const raw = await response.text().catch(() => "") || "";
|
|
294
|
+
const trimmed = raw.trim();
|
|
295
|
+
if (!trimmed) {
|
|
296
|
+
return `Upload failed: ${response.status} ${response.statusText}`.trim();
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const j = JSON.parse(trimmed);
|
|
300
|
+
const d = j.detail;
|
|
301
|
+
if (typeof d === "string") return d;
|
|
302
|
+
if (Array.isArray(d) && d.length > 0) {
|
|
303
|
+
const first = d[0];
|
|
304
|
+
if (first && typeof first.msg === "string") return first.msg;
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
return trimmed.length > 300 ? `${trimmed.slice(0, 300)}\u2026` : trimmed;
|
|
309
|
+
}
|
|
275
310
|
var AgentServerClient = class {
|
|
276
311
|
wsUrl;
|
|
277
312
|
constructor(apiMessagesUrl) {
|
|
@@ -365,6 +400,12 @@ var AgentClient = class {
|
|
|
365
400
|
url = "";
|
|
366
401
|
httpBaseUrl = "";
|
|
367
402
|
agentId = "";
|
|
403
|
+
/** Mirrors ``AgentProvider`` ``agentId`` so HTTP uploads work before WS ``connect`` completes. */
|
|
404
|
+
uploadAgentId = "";
|
|
405
|
+
/** Current chat thread id for ``POST /uploads`` (aligns GCS path with ``hydrate_thread_events``). */
|
|
406
|
+
uploadThreadId = "";
|
|
407
|
+
/** Optional run id for ``POST /uploads`` (metadata only). */
|
|
408
|
+
uploadRunId = "";
|
|
368
409
|
uploadUrl;
|
|
369
410
|
config = {
|
|
370
411
|
organizationId: "",
|
|
@@ -379,7 +420,7 @@ var AgentClient = class {
|
|
|
379
420
|
subscriptionRefs = /* @__PURE__ */ new Map();
|
|
380
421
|
// Track optimistic events for reconciliation
|
|
381
422
|
pendingOptimistic = /* @__PURE__ */ new Map();
|
|
382
|
-
// Pending message promises —
|
|
423
|
+
// Pending message promises — keyed by outbound frame id (same as client_event_id for sends).
|
|
383
424
|
pendingMessages = /* @__PURE__ */ new Map();
|
|
384
425
|
// Track last event ID per thread for gap recovery on reconnect
|
|
385
426
|
lastEventIds = /* @__PURE__ */ new Map();
|
|
@@ -397,14 +438,40 @@ var AgentClient = class {
|
|
|
397
438
|
setUploadUrl(url) {
|
|
398
439
|
this.uploadUrl = url;
|
|
399
440
|
}
|
|
400
|
-
|
|
441
|
+
/** Sync from ``AgentProvider`` ``agentId``; used for ``POST /uploads`` ``agent_id`` (non-WS). */
|
|
442
|
+
setUploadAgentId(agentId) {
|
|
443
|
+
this.uploadAgentId = (agentId ?? "").trim();
|
|
444
|
+
}
|
|
445
|
+
/** Sync active thread id for chat uploads (must match persisted thread for signed URL keys). */
|
|
446
|
+
setUploadThreadId(threadId) {
|
|
447
|
+
this.uploadThreadId = (threadId ?? "").trim();
|
|
448
|
+
}
|
|
449
|
+
setUploadRunId(runId) {
|
|
450
|
+
this.uploadRunId = (runId ?? "").trim();
|
|
451
|
+
}
|
|
452
|
+
async upload(file, options) {
|
|
401
453
|
if (!this.uploadUrl) {
|
|
402
454
|
throw new Error("uploadUrl not configured");
|
|
403
455
|
}
|
|
456
|
+
const aid = (this.uploadAgentId || this.agentId || "").trim();
|
|
457
|
+
if (!aid) {
|
|
458
|
+
throw new Error("agentId is required for upload (set AgentProvider agentId)");
|
|
459
|
+
}
|
|
404
460
|
const formData = new FormData();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
formData.append("
|
|
461
|
+
if (file instanceof File) {
|
|
462
|
+
const partName = options?.filename?.trim() || file.name.trim() || "upload";
|
|
463
|
+
formData.append("file", file, partName);
|
|
464
|
+
} else {
|
|
465
|
+
formData.append("file", file, blobPartFilename(file, options?.filename));
|
|
466
|
+
}
|
|
467
|
+
formData.append("agent_id", aid);
|
|
468
|
+
const tid = this.uploadThreadId.trim();
|
|
469
|
+
if (tid) {
|
|
470
|
+
formData.append("thread_id", tid);
|
|
471
|
+
}
|
|
472
|
+
const rid = this.uploadRunId.trim();
|
|
473
|
+
if (rid) {
|
|
474
|
+
formData.append("run_id", rid);
|
|
408
475
|
}
|
|
409
476
|
const headers = {};
|
|
410
477
|
if (this.config.authToken) {
|
|
@@ -416,7 +483,8 @@ var AgentClient = class {
|
|
|
416
483
|
headers
|
|
417
484
|
});
|
|
418
485
|
if (!response.ok) {
|
|
419
|
-
|
|
486
|
+
const detail = await readHttpErrorMessage(response);
|
|
487
|
+
throw new Error(detail);
|
|
420
488
|
}
|
|
421
489
|
const result = await response.json();
|
|
422
490
|
return {
|
|
@@ -430,6 +498,7 @@ var AgentClient = class {
|
|
|
430
498
|
connect(url, agentId, config) {
|
|
431
499
|
this.url = apiMessagesUrlToWebSocketUrl(url);
|
|
432
500
|
this.agentId = agentId;
|
|
501
|
+
this.uploadAgentId = agentId.trim();
|
|
433
502
|
this.config = config ?? { organizationId: "", workspaceId: "", userId: "" };
|
|
434
503
|
this.isIntentionallyClosed = false;
|
|
435
504
|
try {
|
|
@@ -445,16 +514,29 @@ var AgentClient = class {
|
|
|
445
514
|
this.callbacks.setConnectionStatus("connecting");
|
|
446
515
|
this.createConnection();
|
|
447
516
|
}
|
|
517
|
+
/** Clear the last in-chat send error banner (see ``connection.sendError``). */
|
|
518
|
+
clearSendError() {
|
|
519
|
+
this.callbacks.setSendError(null);
|
|
520
|
+
}
|
|
521
|
+
abortPendingMessages(reason) {
|
|
522
|
+
for (const [clientEventId, pending] of this.pendingMessages) {
|
|
523
|
+
clearTimeout(pending.timer);
|
|
524
|
+
const opt = this.pendingOptimistic.get(clientEventId);
|
|
525
|
+
if (opt?.threadId) {
|
|
526
|
+
this.callbacks.removeOptimistic(opt.threadId, clientEventId);
|
|
527
|
+
}
|
|
528
|
+
this.pendingOptimistic.delete(clientEventId);
|
|
529
|
+
pending.reject(new Error(reason));
|
|
530
|
+
}
|
|
531
|
+
this.pendingMessages.clear();
|
|
532
|
+
}
|
|
448
533
|
disconnect() {
|
|
449
534
|
this.isIntentionallyClosed = true;
|
|
450
535
|
if (this.reconnectTimer) {
|
|
451
536
|
clearTimeout(this.reconnectTimer);
|
|
452
537
|
this.reconnectTimer = null;
|
|
453
538
|
}
|
|
454
|
-
|
|
455
|
-
clearTimeout(pending.timer);
|
|
456
|
-
}
|
|
457
|
-
this.pendingMessages.clear();
|
|
539
|
+
this.abortPendingMessages("Disconnected");
|
|
458
540
|
if (this.ws) {
|
|
459
541
|
this.ws.onopen = null;
|
|
460
542
|
this.ws.onmessage = null;
|
|
@@ -546,6 +628,7 @@ var AgentClient = class {
|
|
|
546
628
|
}
|
|
547
629
|
// --- Messaging ---
|
|
548
630
|
sendMessage(params) {
|
|
631
|
+
this.callbacks.setSendError(null);
|
|
549
632
|
const clientEventId = crypto.randomUUID();
|
|
550
633
|
const content = params.content ? params.content.map(toWireContentPart) : [{ type: "text", text: params.text ?? "" }];
|
|
551
634
|
const displayContent = params.content ? params.content.map((p) => {
|
|
@@ -553,11 +636,27 @@ var AgentClient = class {
|
|
|
553
636
|
case "text":
|
|
554
637
|
return { type: "text", text: p.text };
|
|
555
638
|
case "audio":
|
|
556
|
-
return {
|
|
639
|
+
return {
|
|
640
|
+
type: "audio",
|
|
641
|
+
duration: p.duration,
|
|
642
|
+
...p.url ? { url: p.url } : {},
|
|
643
|
+
...p.mimeType ? { mimeType: p.mimeType } : {},
|
|
644
|
+
...p.filename ? { filename: p.filename } : {}
|
|
645
|
+
};
|
|
557
646
|
case "image":
|
|
558
|
-
return {
|
|
647
|
+
return {
|
|
648
|
+
type: "image",
|
|
649
|
+
...p.url ? { url: p.url } : {},
|
|
650
|
+
...p.mimeType ? { mimeType: p.mimeType } : {},
|
|
651
|
+
...p.filename ? { filename: p.filename } : {}
|
|
652
|
+
};
|
|
559
653
|
case "document":
|
|
560
|
-
return {
|
|
654
|
+
return {
|
|
655
|
+
type: "document",
|
|
656
|
+
...p.url ? { url: p.url } : {},
|
|
657
|
+
...p.mimeType ? { mimeType: p.mimeType } : {},
|
|
658
|
+
...p.filename ? { filename: p.filename } : {}
|
|
659
|
+
};
|
|
561
660
|
default:
|
|
562
661
|
return { type: "text", text: "" };
|
|
563
662
|
}
|
|
@@ -601,23 +700,32 @@ var AgentClient = class {
|
|
|
601
700
|
if (params.metadata) {
|
|
602
701
|
payload.metadata = params.metadata;
|
|
603
702
|
}
|
|
604
|
-
this.
|
|
703
|
+
this.sendRaw({
|
|
704
|
+
id: clientEventId,
|
|
705
|
+
type: "event.create",
|
|
706
|
+
payload
|
|
707
|
+
});
|
|
605
708
|
return promise;
|
|
606
709
|
}
|
|
607
710
|
sendFormResponse(params) {
|
|
711
|
+
this.callbacks.setSendError(null);
|
|
608
712
|
const clientEventId = crypto.randomUUID();
|
|
609
713
|
const promise = this.createPendingPromise(clientEventId);
|
|
610
|
-
this.
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
714
|
+
this.sendRaw({
|
|
715
|
+
id: clientEventId,
|
|
716
|
+
type: "event.create",
|
|
717
|
+
payload: {
|
|
718
|
+
thread_id: params.threadId,
|
|
719
|
+
client_event_id: clientEventId,
|
|
720
|
+
event: {
|
|
721
|
+
type: "form.response",
|
|
722
|
+
actor: "user",
|
|
723
|
+
content: [],
|
|
724
|
+
data: {
|
|
725
|
+
form_event_id: params.formEventId,
|
|
726
|
+
cancelled: params.cancelled,
|
|
727
|
+
values: params.values
|
|
728
|
+
}
|
|
621
729
|
}
|
|
622
730
|
}
|
|
623
731
|
});
|
|
@@ -627,14 +735,20 @@ var AgentClient = class {
|
|
|
627
735
|
createPendingPromise(clientEventId) {
|
|
628
736
|
return new Promise((resolve, reject) => {
|
|
629
737
|
const timer = setTimeout(() => {
|
|
738
|
+
const opt = this.pendingOptimistic.get(clientEventId);
|
|
739
|
+
if (opt?.threadId) {
|
|
740
|
+
this.callbacks.removeOptimistic(opt.threadId, clientEventId);
|
|
741
|
+
}
|
|
742
|
+
this.pendingOptimistic.delete(clientEventId);
|
|
630
743
|
this.pendingMessages.delete(clientEventId);
|
|
631
744
|
reject(new Error("Request timed out waiting for server response"));
|
|
632
745
|
}, SEND_MESSAGE_TIMEOUT);
|
|
633
|
-
this.pendingMessages.set(clientEventId, { resolve, timer });
|
|
746
|
+
this.pendingMessages.set(clientEventId, { resolve, reject, timer });
|
|
634
747
|
});
|
|
635
748
|
}
|
|
636
749
|
createConnection() {
|
|
637
750
|
if (this.ws) {
|
|
751
|
+
this.abortPendingMessages("Connection lost; reconnecting");
|
|
638
752
|
this.ws.onopen = null;
|
|
639
753
|
this.ws.onmessage = null;
|
|
640
754
|
this.ws.onclose = null;
|
|
@@ -702,9 +816,26 @@ var AgentClient = class {
|
|
|
702
816
|
this.callbacks.setConnectionStatus("error", frame.payload.error?.message);
|
|
703
817
|
}
|
|
704
818
|
} else if (!frame.payload.ok && frame.payload.error) {
|
|
819
|
+
const ackId = frame.payload.ack_id;
|
|
820
|
+
const err = frame.payload.error;
|
|
821
|
+
if (ackId) {
|
|
822
|
+
const pending = this.pendingMessages.get(ackId);
|
|
823
|
+
if (pending) {
|
|
824
|
+
clearTimeout(pending.timer);
|
|
825
|
+
this.pendingMessages.delete(ackId);
|
|
826
|
+
const msg = err.message || "Request rejected";
|
|
827
|
+
const opt = this.pendingOptimistic.get(ackId);
|
|
828
|
+
if (opt?.threadId) {
|
|
829
|
+
this.callbacks.removeOptimistic(opt.threadId, ackId);
|
|
830
|
+
}
|
|
831
|
+
this.pendingOptimistic.delete(ackId);
|
|
832
|
+
pending.reject(new Error(msg));
|
|
833
|
+
this.callbacks.setSendError(msg);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
705
836
|
this.onError?.({
|
|
706
|
-
code:
|
|
707
|
-
message:
|
|
837
|
+
code: err.code,
|
|
838
|
+
message: err.message,
|
|
708
839
|
frameType: "ack"
|
|
709
840
|
});
|
|
710
841
|
}
|
|
@@ -741,6 +872,7 @@ var AgentClient = class {
|
|
|
741
872
|
case "event.append": {
|
|
742
873
|
const events = this.resolveEventUrls(frame.payload.events.map(toThreadEvent));
|
|
743
874
|
const resolvedClientEventIds = [];
|
|
875
|
+
const pendingIdsToResolve = [];
|
|
744
876
|
for (const event of events) {
|
|
745
877
|
if (event.clientEventId && this.pendingOptimistic.has(event.clientEventId)) {
|
|
746
878
|
resolvedClientEventIds.push(event.clientEventId);
|
|
@@ -750,8 +882,7 @@ var AgentClient = class {
|
|
|
750
882
|
const pendingMessage = this.pendingMessages.get(event.clientEventId);
|
|
751
883
|
if (pendingMessage) {
|
|
752
884
|
clearTimeout(pendingMessage.timer);
|
|
753
|
-
|
|
754
|
-
this.pendingMessages.delete(event.clientEventId);
|
|
885
|
+
pendingIdsToResolve.push(event.clientEventId);
|
|
755
886
|
}
|
|
756
887
|
}
|
|
757
888
|
}
|
|
@@ -764,6 +895,14 @@ var AgentClient = class {
|
|
|
764
895
|
} else {
|
|
765
896
|
this.callbacks.onEventAppend(frame.payload.thread_id, events);
|
|
766
897
|
}
|
|
898
|
+
const tid = frame.payload.thread_id;
|
|
899
|
+
for (const id of pendingIdsToResolve) {
|
|
900
|
+
const pending = this.pendingMessages.get(id);
|
|
901
|
+
if (pending) {
|
|
902
|
+
pending.resolve({ threadId: tid });
|
|
903
|
+
this.pendingMessages.delete(id);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
767
906
|
break;
|
|
768
907
|
}
|
|
769
908
|
case "event.list.result": {
|
|
@@ -816,6 +955,7 @@ var AgentClient = class {
|
|
|
816
955
|
}
|
|
817
956
|
scheduleReconnect() {
|
|
818
957
|
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
958
|
+
this.abortPendingMessages("Connection lost after maximum retries");
|
|
819
959
|
this.callbacks.setReconnectFailed();
|
|
820
960
|
this.callbacks.setConnectionStatus("error", "Connection lost after maximum retries");
|
|
821
961
|
return;
|
|
@@ -850,6 +990,43 @@ var AgentClient = class {
|
|
|
850
990
|
});
|
|
851
991
|
}
|
|
852
992
|
};
|
|
993
|
+
function mergeDisplayContentParts(prev, next) {
|
|
994
|
+
if (!next?.length) return prev;
|
|
995
|
+
if (!prev?.length) return next;
|
|
996
|
+
const out = [];
|
|
997
|
+
const len = Math.max(prev.length, next.length);
|
|
998
|
+
for (let i = 0; i < len; i++) {
|
|
999
|
+
const p = prev[i];
|
|
1000
|
+
const n = next[i];
|
|
1001
|
+
if (!n) {
|
|
1002
|
+
if (p) out.push(p);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (!p) {
|
|
1006
|
+
out.push(n);
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (p.type !== n.type) {
|
|
1010
|
+
out.push(n);
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (n.type === "image" || n.type === "audio" || n.type === "document") {
|
|
1014
|
+
const url = n.url ?? ("url" in p ? p.url : void 0);
|
|
1015
|
+
out.push({ ...p, ...n, ...url ? { url } : {} });
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
out.push(n);
|
|
1019
|
+
}
|
|
1020
|
+
return out;
|
|
1021
|
+
}
|
|
1022
|
+
function mergeThreadEventById(prev, next) {
|
|
1023
|
+
const content = mergeDisplayContentParts(prev.content, next.content);
|
|
1024
|
+
return {
|
|
1025
|
+
...prev,
|
|
1026
|
+
...next,
|
|
1027
|
+
...content !== void 0 ? { content } : {}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
853
1030
|
function extractFormStates(events, existing) {
|
|
854
1031
|
let result = existing;
|
|
855
1032
|
for (const e of events) {
|
|
@@ -872,6 +1049,7 @@ function createAgentStore() {
|
|
|
872
1049
|
connection: {
|
|
873
1050
|
status: "idle",
|
|
874
1051
|
error: null,
|
|
1052
|
+
sendError: null,
|
|
875
1053
|
reconnectAttempts: 0,
|
|
876
1054
|
reconnectFailed: false
|
|
877
1055
|
},
|
|
@@ -900,7 +1078,15 @@ function createAgentStore() {
|
|
|
900
1078
|
loadingThreads: /* @__PURE__ */ new Set(),
|
|
901
1079
|
actions: {
|
|
902
1080
|
setConnectionStatus: (status, error) => set((s) => ({
|
|
903
|
-
connection: {
|
|
1081
|
+
connection: {
|
|
1082
|
+
...s.connection,
|
|
1083
|
+
status,
|
|
1084
|
+
error: error ?? null,
|
|
1085
|
+
...status === "connected" ? { sendError: null } : {}
|
|
1086
|
+
}
|
|
1087
|
+
})),
|
|
1088
|
+
setSendError: (message) => set((s) => ({
|
|
1089
|
+
connection: { ...s.connection, sendError: message }
|
|
904
1090
|
})),
|
|
905
1091
|
setReconnectFailed: () => set((s) => ({
|
|
906
1092
|
connection: { ...s.connection, reconnectFailed: true }
|
|
@@ -988,8 +1174,13 @@ function createAgentStore() {
|
|
|
988
1174
|
mergedEvents = snapshotEvents;
|
|
989
1175
|
} else {
|
|
990
1176
|
const existingIds = new Set(existing.map((e) => e.id));
|
|
1177
|
+
const snapById = new Map(snapshotEvents.map((e) => [e.id, e]));
|
|
1178
|
+
const mergedExisting = existing.map((e) => {
|
|
1179
|
+
const snap = snapById.get(e.id);
|
|
1180
|
+
return snap ? mergeThreadEventById(e, snap) : e;
|
|
1181
|
+
});
|
|
991
1182
|
const newFromSnapshot = snapshotEvents.filter((e) => !existingIds.has(e.id));
|
|
992
|
-
mergedEvents = newFromSnapshot.length > 0 ? [...
|
|
1183
|
+
mergedEvents = newFromSnapshot.length > 0 ? [...mergedExisting, ...newFromSnapshot] : mergedExisting;
|
|
993
1184
|
}
|
|
994
1185
|
const optimistic = { ...s.optimistic };
|
|
995
1186
|
const optimisticKeys = { ...s.optimisticKeys };
|
|
@@ -1013,7 +1204,8 @@ function createAgentStore() {
|
|
|
1013
1204
|
const appends = [];
|
|
1014
1205
|
for (const e of newEvents) {
|
|
1015
1206
|
if (existingIds.has(e.id)) {
|
|
1016
|
-
|
|
1207
|
+
const prev = existing.find((x) => x.id === e.id);
|
|
1208
|
+
updates.set(e.id, mergeThreadEventById(prev, e));
|
|
1017
1209
|
} else {
|
|
1018
1210
|
appends.push(e);
|
|
1019
1211
|
}
|
|
@@ -1027,6 +1219,10 @@ function createAgentStore() {
|
|
|
1027
1219
|
reconcileEvents: (threadId, newEvents, resolvedClientEventIds) => set((s) => {
|
|
1028
1220
|
const existing = s.events[threadId] ?? [];
|
|
1029
1221
|
const existingIds = new Set(existing.map((e) => e.id));
|
|
1222
|
+
const mergedExisting = newEvents.length > 0 ? existing.map((e) => {
|
|
1223
|
+
const up = newEvents.find((n) => n.id === e.id);
|
|
1224
|
+
return up ? mergeThreadEventById(e, up) : e;
|
|
1225
|
+
}) : existing;
|
|
1030
1226
|
const unique = newEvents.filter((e) => !existingIds.has(e.id));
|
|
1031
1227
|
let optEvents = s.optimistic[threadId] ?? [];
|
|
1032
1228
|
let optKeys = s.optimisticKeys[threadId] ?? [];
|
|
@@ -1040,7 +1236,7 @@ function createAgentStore() {
|
|
|
1040
1236
|
}
|
|
1041
1237
|
}
|
|
1042
1238
|
return {
|
|
1043
|
-
events: { ...s.events, [threadId]: [...
|
|
1239
|
+
events: { ...s.events, [threadId]: [...mergedExisting, ...unique] },
|
|
1044
1240
|
optimistic: { ...s.optimistic, [threadId]: optEvents },
|
|
1045
1241
|
optimisticKeys: { ...s.optimisticKeys, [threadId]: optKeys }
|
|
1046
1242
|
};
|
|
@@ -1090,7 +1286,10 @@ function createAgentStore() {
|
|
|
1090
1286
|
const subscriptions = new Set(s.subscriptions);
|
|
1091
1287
|
subscriptions.add(threadId);
|
|
1092
1288
|
const loadingThreads = new Set(s.loadingThreads);
|
|
1093
|
-
|
|
1289
|
+
const alreadyHadEvents = (s.events[threadId]?.length ?? 0) > 0;
|
|
1290
|
+
if (!alreadyHadEvents) {
|
|
1291
|
+
loadingThreads.add(threadId);
|
|
1292
|
+
}
|
|
1094
1293
|
return { subscriptions, loadingThreads };
|
|
1095
1294
|
}),
|
|
1096
1295
|
removeSubscription: (threadId) => set((s) => {
|
|
@@ -1234,6 +1433,9 @@ function AgentProvider({
|
|
|
1234
1433
|
react.useEffect(() => {
|
|
1235
1434
|
clientRef.current?.setUploadUrl(uploadUrl);
|
|
1236
1435
|
}, [uploadUrl]);
|
|
1436
|
+
react.useEffect(() => {
|
|
1437
|
+
clientRef.current?.setUploadAgentId(agentId);
|
|
1438
|
+
}, [agentId]);
|
|
1237
1439
|
const identityIdsKey = identityIds?.join(",") ?? "";
|
|
1238
1440
|
react.useEffect(() => {
|
|
1239
1441
|
if (!url || !agentId) return;
|
|
@@ -1403,9 +1605,13 @@ function useConnection() {
|
|
|
1403
1605
|
const { client } = useAgentContext();
|
|
1404
1606
|
const status = useStore((s) => s.connection.status);
|
|
1405
1607
|
const error = useStore((s) => s.connection.error);
|
|
1608
|
+
const sendError = useStore((s) => s.connection.sendError);
|
|
1406
1609
|
const reconnectFailed = useStore((s) => s.connection.reconnectFailed);
|
|
1407
1610
|
const reconnect = react.useCallback(() => client.retry(), [client]);
|
|
1408
|
-
|
|
1611
|
+
const dismissSendError = react.useCallback(() => {
|
|
1612
|
+
client.clearSendError();
|
|
1613
|
+
}, [client]);
|
|
1614
|
+
return { status, error, sendError, dismissSendError, reconnectFailed, reconnect };
|
|
1409
1615
|
}
|
|
1410
1616
|
function useFeedback(targetId, targetType = "event") {
|
|
1411
1617
|
const { client } = useAgentContext();
|
|
@@ -1534,11 +1740,15 @@ function useFormFileUpload() {
|
|
|
1534
1740
|
setIsUploading(true);
|
|
1535
1741
|
setError(null);
|
|
1536
1742
|
try {
|
|
1743
|
+
const aid = (params.agentId || "").trim();
|
|
1744
|
+
if (!aid) {
|
|
1745
|
+
throw new Error("agentId is required for upload");
|
|
1746
|
+
}
|
|
1537
1747
|
const formData = new FormData();
|
|
1538
1748
|
formData.append("file", params.file);
|
|
1539
1749
|
formData.append("field_id", params.fieldId);
|
|
1540
1750
|
formData.append("source", "chat");
|
|
1541
|
-
formData.append("agent_id",
|
|
1751
|
+
formData.append("agent_id", aid);
|
|
1542
1752
|
formData.append("run_id", params.runId);
|
|
1543
1753
|
formData.append("thread_id", params.threadId);
|
|
1544
1754
|
const response = await fetch(params.uploadUrl, {
|
|
@@ -1690,11 +1900,11 @@ function useUpload() {
|
|
|
1690
1900
|
const [isUploading, setIsUploading] = react.useState(false);
|
|
1691
1901
|
const activeCountRef = react.useRef(0);
|
|
1692
1902
|
const upload = react.useCallback(
|
|
1693
|
-
async (file) => {
|
|
1903
|
+
async (file, options) => {
|
|
1694
1904
|
activeCountRef.current++;
|
|
1695
1905
|
setIsUploading(true);
|
|
1696
1906
|
try {
|
|
1697
|
-
return await client.upload(file);
|
|
1907
|
+
return await client.upload(file, options);
|
|
1698
1908
|
} finally {
|
|
1699
1909
|
activeCountRef.current--;
|
|
1700
1910
|
if (activeCountRef.current === 0) {
|
|
@@ -1714,6 +1924,7 @@ exports.isSettingsFieldGroup = isSettingsFieldGroup;
|
|
|
1714
1924
|
exports.toFormElement = toFormElement;
|
|
1715
1925
|
exports.toFormEventData = toFormEventData;
|
|
1716
1926
|
exports.toFormResponseEventData = toFormResponseEventData;
|
|
1927
|
+
exports.useAgentContext = useAgentContext;
|
|
1717
1928
|
exports.useAnalytics = useAnalytics;
|
|
1718
1929
|
exports.useAudioRecorder = useAudioRecorder;
|
|
1719
1930
|
exports.useConnection = useConnection;
|