dooers-agents-client 0.1.0 → 0.2.0
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/README.md +6 -4
- package/dist/main.cjs +219 -14
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.cts +65 -11
- package/dist/main.d.ts +65 -11
- package/dist/main.js +219 -16
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,9 @@ function Chat({ threadId }: { threadId: string }) {
|
|
|
71
71
|
userId="user-1"
|
|
72
72
|
userName="Alice"
|
|
73
73
|
userEmail="alice@example.com"
|
|
74
|
-
|
|
74
|
+
systemRole="user" // "admin" | "user" (default: "user")
|
|
75
|
+
organizationRole="member" // "owner" | "manager" | "member" (default: "member")
|
|
76
|
+
workspaceRole="member" // "manager" | "member" (default: "member")
|
|
75
77
|
authToken="sk-..." // Optional authentication
|
|
76
78
|
onError={(err) => console.error(err.code, err.message)}
|
|
77
79
|
>
|
|
@@ -212,11 +214,11 @@ All public types are camelCase. The SDK transforms the wire format (snake_case)
|
|
|
212
214
|
|
|
213
215
|
```typescript
|
|
214
216
|
import type {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
User, // { userId, userName, userEmail, systemRole, organizationRole, workspaceRole }
|
|
218
|
+
Thread, // { id, workerId, organizationId, workspaceId, owner, users, title, ... }
|
|
219
|
+
ThreadEvent, // { id, threadId, type, actor, author, user, content, data, createdAt, ... }
|
|
217
220
|
Run, // { id, threadId, agentId, status, startedAt, endedAt, error }
|
|
218
221
|
ContentPart, // TextPart | ImagePart | DocumentPart
|
|
219
|
-
Metadata, // { userId, userName, userEmail, userRole, organizationId, workspaceId }
|
|
220
222
|
ConnectionStatus, // "idle" | "connecting" | "connected" | "disconnected" | "error"
|
|
221
223
|
Actor, // "user" | "assistant" | "system" | "tool"
|
|
222
224
|
EventType, // "message" | "tool.call" | "tool.result" | "tool.transaction" | ...
|
package/dist/main.cjs
CHANGED
|
@@ -37,14 +37,36 @@ function toThread(w) {
|
|
|
37
37
|
lastEventAt: w.last_event_at
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function toDisplayContentPart(w) {
|
|
41
41
|
switch (w.type) {
|
|
42
42
|
case "text":
|
|
43
43
|
return { type: "text", text: w.text };
|
|
44
|
+
case "audio":
|
|
45
|
+
return {
|
|
46
|
+
type: "audio",
|
|
47
|
+
url: w.url,
|
|
48
|
+
mimeType: w.mime_type,
|
|
49
|
+
duration: w.duration,
|
|
50
|
+
filename: w.filename
|
|
51
|
+
};
|
|
44
52
|
case "image":
|
|
45
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
type: "image",
|
|
55
|
+
url: w.url,
|
|
56
|
+
mimeType: w.mime_type,
|
|
57
|
+
width: w.width,
|
|
58
|
+
height: w.height,
|
|
59
|
+
alt: w.alt,
|
|
60
|
+
filename: w.filename
|
|
61
|
+
};
|
|
46
62
|
case "document":
|
|
47
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
type: "document",
|
|
65
|
+
url: w.url,
|
|
66
|
+
filename: w.filename,
|
|
67
|
+
mimeType: w.mime_type,
|
|
68
|
+
sizeBytes: w.size_bytes
|
|
69
|
+
};
|
|
48
70
|
}
|
|
49
71
|
}
|
|
50
72
|
function toThreadEvent(w) {
|
|
@@ -56,7 +78,7 @@ function toThreadEvent(w) {
|
|
|
56
78
|
actor: w.actor,
|
|
57
79
|
author: w.author,
|
|
58
80
|
user: toUser(w.user),
|
|
59
|
-
content: w.content?.map(
|
|
81
|
+
content: w.content?.map(toDisplayContentPart),
|
|
60
82
|
data: w.data,
|
|
61
83
|
createdAt: w.created_at,
|
|
62
84
|
clientEventId: w.client_event_id
|
|
@@ -119,10 +141,12 @@ function toWireContentPart(p) {
|
|
|
119
141
|
switch (p.type) {
|
|
120
142
|
case "text":
|
|
121
143
|
return { type: "text", text: p.text };
|
|
144
|
+
case "audio":
|
|
145
|
+
return { type: "audio", ref_id: p.refId, duration: p.duration };
|
|
122
146
|
case "image":
|
|
123
|
-
return { type: "image",
|
|
147
|
+
return { type: "image", ref_id: p.refId };
|
|
124
148
|
case "document":
|
|
125
|
-
return { type: "document",
|
|
149
|
+
return { type: "document", ref_id: p.refId };
|
|
126
150
|
}
|
|
127
151
|
}
|
|
128
152
|
|
|
@@ -136,6 +160,7 @@ var WorkerClient = class {
|
|
|
136
160
|
onError = null;
|
|
137
161
|
url = "";
|
|
138
162
|
workerId = "";
|
|
163
|
+
uploadUrl;
|
|
139
164
|
config = {
|
|
140
165
|
organizationId: "",
|
|
141
166
|
workspaceId: "",
|
|
@@ -164,6 +189,35 @@ var WorkerClient = class {
|
|
|
164
189
|
setOnError(cb) {
|
|
165
190
|
this.onError = cb;
|
|
166
191
|
}
|
|
192
|
+
setUploadUrl(url) {
|
|
193
|
+
this.uploadUrl = url;
|
|
194
|
+
}
|
|
195
|
+
async upload(file) {
|
|
196
|
+
if (!this.uploadUrl) {
|
|
197
|
+
throw new Error("uploadUrl not configured");
|
|
198
|
+
}
|
|
199
|
+
const formData = new FormData();
|
|
200
|
+
formData.append("file", file);
|
|
201
|
+
const headers = {};
|
|
202
|
+
if (this.config.authToken) {
|
|
203
|
+
headers.Authorization = `Bearer ${this.config.authToken}`;
|
|
204
|
+
}
|
|
205
|
+
const response = await fetch(this.uploadUrl, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
body: formData,
|
|
208
|
+
headers
|
|
209
|
+
});
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
212
|
+
}
|
|
213
|
+
const result = await response.json();
|
|
214
|
+
return {
|
|
215
|
+
refId: result.ref_id,
|
|
216
|
+
mimeType: result.mime_type,
|
|
217
|
+
filename: result.filename,
|
|
218
|
+
sizeBytes: result.size_bytes
|
|
219
|
+
};
|
|
220
|
+
}
|
|
167
221
|
connect(url, workerId, config) {
|
|
168
222
|
this.url = url;
|
|
169
223
|
this.workerId = workerId;
|
|
@@ -270,6 +324,20 @@ var WorkerClient = class {
|
|
|
270
324
|
sendMessage(params) {
|
|
271
325
|
const clientEventId = crypto.randomUUID();
|
|
272
326
|
const content = params.content ? params.content.map(toWireContentPart) : [{ type: "text", text: params.text ?? "" }];
|
|
327
|
+
const displayContent = params.content ? params.content.map((p) => {
|
|
328
|
+
switch (p.type) {
|
|
329
|
+
case "text":
|
|
330
|
+
return { type: "text", text: p.text };
|
|
331
|
+
case "audio":
|
|
332
|
+
return { type: "audio", duration: p.duration };
|
|
333
|
+
case "image":
|
|
334
|
+
return { type: "image" };
|
|
335
|
+
case "document":
|
|
336
|
+
return { type: "document" };
|
|
337
|
+
default:
|
|
338
|
+
return { type: "text", text: "" };
|
|
339
|
+
}
|
|
340
|
+
}) : [{ type: "text", text: params.text ?? "" }];
|
|
273
341
|
const optimisticEvent = {
|
|
274
342
|
id: `optimistic-${clientEventId}`,
|
|
275
343
|
threadId: params.threadId ?? "",
|
|
@@ -285,7 +353,7 @@ var WorkerClient = class {
|
|
|
285
353
|
organizationRole: this.config.organizationRole ?? "member",
|
|
286
354
|
workspaceRole: this.config.workspaceRole ?? "member"
|
|
287
355
|
},
|
|
288
|
-
content:
|
|
356
|
+
content: displayContent,
|
|
289
357
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
290
358
|
};
|
|
291
359
|
if (params.threadId) {
|
|
@@ -420,13 +488,11 @@ var WorkerClient = class {
|
|
|
420
488
|
}
|
|
421
489
|
case "event.append": {
|
|
422
490
|
const events = frame.payload.events.map(toThreadEvent);
|
|
491
|
+
const resolvedClientEventIds = [];
|
|
423
492
|
for (const event of events) {
|
|
424
493
|
if (event.clientEventId && this.pendingOptimistic.has(event.clientEventId)) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
this.callbacks.removeOptimistic(pending.threadId, event.clientEventId);
|
|
428
|
-
this.pendingOptimistic.delete(event.clientEventId);
|
|
429
|
-
}
|
|
494
|
+
resolvedClientEventIds.push(event.clientEventId);
|
|
495
|
+
this.pendingOptimistic.delete(event.clientEventId);
|
|
430
496
|
}
|
|
431
497
|
if (event.clientEventId && this.pendingMessages.has(event.clientEventId)) {
|
|
432
498
|
const pendingMessage = this.pendingMessages.get(event.clientEventId);
|
|
@@ -441,7 +507,11 @@ var WorkerClient = class {
|
|
|
441
507
|
if (lastEvent) {
|
|
442
508
|
this.lastEventIds.set(frame.payload.thread_id, lastEvent.id);
|
|
443
509
|
}
|
|
444
|
-
|
|
510
|
+
if (resolvedClientEventIds.length > 0) {
|
|
511
|
+
this.callbacks.reconcileEvents(frame.payload.thread_id, events, resolvedClientEventIds);
|
|
512
|
+
} else {
|
|
513
|
+
this.callbacks.onEventAppend(frame.payload.thread_id, events);
|
|
514
|
+
}
|
|
445
515
|
break;
|
|
446
516
|
}
|
|
447
517
|
case "event.list.result": {
|
|
@@ -633,6 +703,27 @@ function createWorkerStore() {
|
|
|
633
703
|
events: { ...s.events, [threadId]: [...existing, ...unique] }
|
|
634
704
|
};
|
|
635
705
|
}),
|
|
706
|
+
reconcileEvents: (threadId, newEvents, resolvedClientEventIds) => set((s) => {
|
|
707
|
+
const existing = s.events[threadId] ?? [];
|
|
708
|
+
const existingIds = new Set(existing.map((e) => e.id));
|
|
709
|
+
const unique = newEvents.filter((e) => !existingIds.has(e.id));
|
|
710
|
+
let optEvents = s.optimistic[threadId] ?? [];
|
|
711
|
+
let optKeys = s.optimisticKeys[threadId] ?? [];
|
|
712
|
+
for (const clientEventId of resolvedClientEventIds) {
|
|
713
|
+
const idx = optKeys.indexOf(clientEventId);
|
|
714
|
+
if (idx >= 0) {
|
|
715
|
+
optEvents = [...optEvents];
|
|
716
|
+
optEvents.splice(idx, 1);
|
|
717
|
+
optKeys = [...optKeys];
|
|
718
|
+
optKeys.splice(idx, 1);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
events: { ...s.events, [threadId]: [...existing, ...unique] },
|
|
723
|
+
optimistic: { ...s.optimistic, [threadId]: optEvents },
|
|
724
|
+
optimisticKeys: { ...s.optimisticKeys, [threadId]: optKeys }
|
|
725
|
+
};
|
|
726
|
+
}),
|
|
636
727
|
onEventListResult: (threadId, olderEvents, cursor, hasMore) => set((s) => {
|
|
637
728
|
const existing = s.events[threadId] ?? [];
|
|
638
729
|
const existingIds = new Set(existing.map((e) => e.id));
|
|
@@ -748,6 +839,7 @@ function WorkerProvider({
|
|
|
748
839
|
organizationRole,
|
|
749
840
|
workspaceRole,
|
|
750
841
|
authToken,
|
|
842
|
+
uploadUrl,
|
|
751
843
|
onError,
|
|
752
844
|
children
|
|
753
845
|
}) {
|
|
@@ -760,6 +852,9 @@ function WorkerProvider({
|
|
|
760
852
|
react.useEffect(() => {
|
|
761
853
|
clientRef.current?.setOnError(onError ?? null);
|
|
762
854
|
}, [onError]);
|
|
855
|
+
react.useEffect(() => {
|
|
856
|
+
clientRef.current?.setUploadUrl(uploadUrl);
|
|
857
|
+
}, [uploadUrl]);
|
|
763
858
|
react.useEffect(() => {
|
|
764
859
|
if (!url || !workerId) return;
|
|
765
860
|
clientRef.current?.connect(url, workerId, {
|
|
@@ -818,6 +913,93 @@ function useAnalytics() {
|
|
|
818
913
|
const unsubscribe = react.useCallback(() => client.unsubscribeAnalytics(), [client]);
|
|
819
914
|
return { events, counters, subscribe, unsubscribe };
|
|
820
915
|
}
|
|
916
|
+
function getPreferredMimeType() {
|
|
917
|
+
if (typeof MediaRecorder !== "undefined") {
|
|
918
|
+
if (MediaRecorder.isTypeSupported("audio/webm;codecs=opus")) return "audio/webm;codecs=opus";
|
|
919
|
+
if (MediaRecorder.isTypeSupported("audio/webm")) return "audio/webm";
|
|
920
|
+
if (MediaRecorder.isTypeSupported("audio/mp4")) return "audio/mp4";
|
|
921
|
+
}
|
|
922
|
+
return "audio/webm";
|
|
923
|
+
}
|
|
924
|
+
function useAudioRecorder() {
|
|
925
|
+
const [isRecording, setIsRecording] = react.useState(false);
|
|
926
|
+
const [duration, setDuration] = react.useState(0);
|
|
927
|
+
const mediaRecorderRef = react.useRef(null);
|
|
928
|
+
const streamRef = react.useRef(null);
|
|
929
|
+
const chunksRef = react.useRef([]);
|
|
930
|
+
const timerRef = react.useRef(null);
|
|
931
|
+
const startTimeRef = react.useRef(0);
|
|
932
|
+
const resolveStopRef = react.useRef(null);
|
|
933
|
+
const releaseStream = react.useCallback(() => {
|
|
934
|
+
streamRef.current?.getTracks().forEach((t) => {
|
|
935
|
+
t.stop();
|
|
936
|
+
});
|
|
937
|
+
streamRef.current = null;
|
|
938
|
+
}, []);
|
|
939
|
+
const clearTimer = react.useCallback(() => {
|
|
940
|
+
if (timerRef.current) {
|
|
941
|
+
clearInterval(timerRef.current);
|
|
942
|
+
timerRef.current = null;
|
|
943
|
+
}
|
|
944
|
+
}, []);
|
|
945
|
+
react.useEffect(() => {
|
|
946
|
+
return () => {
|
|
947
|
+
if (mediaRecorderRef.current?.state === "recording") {
|
|
948
|
+
mediaRecorderRef.current.stop();
|
|
949
|
+
}
|
|
950
|
+
releaseStream();
|
|
951
|
+
clearTimer();
|
|
952
|
+
};
|
|
953
|
+
}, [releaseStream, clearTimer]);
|
|
954
|
+
const start = react.useCallback(async () => {
|
|
955
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
956
|
+
streamRef.current = stream;
|
|
957
|
+
const mimeType = getPreferredMimeType();
|
|
958
|
+
const recorder = new MediaRecorder(stream, { mimeType });
|
|
959
|
+
mediaRecorderRef.current = recorder;
|
|
960
|
+
chunksRef.current = [];
|
|
961
|
+
recorder.ondataavailable = (e) => {
|
|
962
|
+
if (e.data.size > 0) chunksRef.current.push(e.data);
|
|
963
|
+
};
|
|
964
|
+
recorder.onstop = () => {
|
|
965
|
+
const blob = new Blob(chunksRef.current, { type: mimeType.split(";")[0] });
|
|
966
|
+
releaseStream();
|
|
967
|
+
clearTimer();
|
|
968
|
+
resolveStopRef.current?.(blob);
|
|
969
|
+
resolveStopRef.current = null;
|
|
970
|
+
};
|
|
971
|
+
startTimeRef.current = Date.now();
|
|
972
|
+
setDuration(0);
|
|
973
|
+
timerRef.current = setInterval(() => {
|
|
974
|
+
setDuration(Math.floor((Date.now() - startTimeRef.current) / 1e3));
|
|
975
|
+
}, 1e3);
|
|
976
|
+
recorder.start();
|
|
977
|
+
setIsRecording(true);
|
|
978
|
+
}, [releaseStream, clearTimer]);
|
|
979
|
+
const stop = react.useCallback(() => {
|
|
980
|
+
return new Promise((resolve, reject) => {
|
|
981
|
+
if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
|
|
982
|
+
reject(new Error("No active recording to stop"));
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
resolveStopRef.current = resolve;
|
|
986
|
+
mediaRecorderRef.current.stop();
|
|
987
|
+
setIsRecording(false);
|
|
988
|
+
setDuration(0);
|
|
989
|
+
});
|
|
990
|
+
}, []);
|
|
991
|
+
const cancel = react.useCallback(() => {
|
|
992
|
+
resolveStopRef.current = null;
|
|
993
|
+
if (mediaRecorderRef.current?.state === "recording") {
|
|
994
|
+
mediaRecorderRef.current.stop();
|
|
995
|
+
}
|
|
996
|
+
releaseStream();
|
|
997
|
+
clearTimer();
|
|
998
|
+
setIsRecording(false);
|
|
999
|
+
setDuration(0);
|
|
1000
|
+
}, [releaseStream, clearTimer]);
|
|
1001
|
+
return { start, stop, cancel, isRecording, duration };
|
|
1002
|
+
}
|
|
821
1003
|
function useConnection() {
|
|
822
1004
|
const { client } = useWorkerContext();
|
|
823
1005
|
const status = useStore((s) => s.connection.status);
|
|
@@ -884,7 +1066,7 @@ function useThreadDetails(threadId) {
|
|
|
884
1066
|
const confirmed = useStore((s) => threadId ? s.events[threadId] ?? EMPTY_ARRAY : EMPTY_ARRAY);
|
|
885
1067
|
const runs = useStore((s) => threadId ? s.runs[threadId] ?? EMPTY_RUNS : EMPTY_RUNS);
|
|
886
1068
|
const events = react.useMemo(
|
|
887
|
-
() => optimistic.length ? [...
|
|
1069
|
+
() => optimistic.length ? [...confirmed, ...optimistic] : confirmed,
|
|
888
1070
|
[optimistic, confirmed]
|
|
889
1071
|
);
|
|
890
1072
|
return { thread, events, runs, isLoading, isWaiting };
|
|
@@ -923,10 +1105,32 @@ function useThreadsActions() {
|
|
|
923
1105
|
const loadMore = react.useCallback((limit) => client.loadMoreThreads(limit), [client]);
|
|
924
1106
|
return { deleteThread, loadMore };
|
|
925
1107
|
}
|
|
1108
|
+
function useUpload() {
|
|
1109
|
+
const { client } = useWorkerContext();
|
|
1110
|
+
const [isUploading, setIsUploading] = react.useState(false);
|
|
1111
|
+
const activeCountRef = react.useRef(0);
|
|
1112
|
+
const upload = react.useCallback(
|
|
1113
|
+
async (file) => {
|
|
1114
|
+
activeCountRef.current++;
|
|
1115
|
+
setIsUploading(true);
|
|
1116
|
+
try {
|
|
1117
|
+
return await client.upload(file);
|
|
1118
|
+
} finally {
|
|
1119
|
+
activeCountRef.current--;
|
|
1120
|
+
if (activeCountRef.current === 0) {
|
|
1121
|
+
setIsUploading(false);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
[client]
|
|
1126
|
+
);
|
|
1127
|
+
return { upload, isUploading };
|
|
1128
|
+
}
|
|
926
1129
|
|
|
927
1130
|
exports.WorkerProvider = WorkerProvider;
|
|
928
1131
|
exports.isSettingsFieldGroup = isSettingsFieldGroup;
|
|
929
1132
|
exports.useAnalytics = useAnalytics;
|
|
1133
|
+
exports.useAudioRecorder = useAudioRecorder;
|
|
930
1134
|
exports.useConnection = useConnection;
|
|
931
1135
|
exports.useFeedback = useFeedback;
|
|
932
1136
|
exports.useMessage = useMessage;
|
|
@@ -935,5 +1139,6 @@ exports.useThreadDetails = useThreadDetails;
|
|
|
935
1139
|
exports.useThreadEvents = useThreadEvents;
|
|
936
1140
|
exports.useThreadsActions = useThreadsActions;
|
|
937
1141
|
exports.useThreadsList = useThreadsList;
|
|
1142
|
+
exports.useUpload = useUpload;
|
|
938
1143
|
//# sourceMappingURL=main.cjs.map
|
|
939
1144
|
//# sourceMappingURL=main.cjs.map
|