@personaai/react 0.3.5 → 0.5.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/dist/index.cjs +244 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -4
- package/dist/index.d.ts +70 -4
- package/dist/index.js +242 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,6 +42,181 @@ function usePersonaContext() {
|
|
|
42
42
|
|
|
43
43
|
// src/hooks/useChat.ts
|
|
44
44
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
45
|
+
|
|
46
|
+
// src/streaming.ts
|
|
47
|
+
function supportsStreamingFetch() {
|
|
48
|
+
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return typeof fetch !== "undefined" && typeof ReadableStream !== "undefined";
|
|
52
|
+
}
|
|
53
|
+
function fetchReader(response, controller) {
|
|
54
|
+
const reader = response.body.getReader();
|
|
55
|
+
const decoder = new TextDecoder("utf-8");
|
|
56
|
+
return {
|
|
57
|
+
async read() {
|
|
58
|
+
const { done, value } = await reader.read();
|
|
59
|
+
if (done) return { done: true };
|
|
60
|
+
return { done: false, value: decoder.decode(value, { stream: true }) };
|
|
61
|
+
},
|
|
62
|
+
cancel() {
|
|
63
|
+
controller.abort();
|
|
64
|
+
void reader.cancel().catch(() => {
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function xhrStream(opts) {
|
|
70
|
+
return new Promise((resolveStream, rejectStream) => {
|
|
71
|
+
const xhr = new XMLHttpRequest();
|
|
72
|
+
xhr.open("POST", opts.url);
|
|
73
|
+
for (const [key, val] of Object.entries(opts.headers)) xhr.setRequestHeader(key, val);
|
|
74
|
+
let consumed = 0;
|
|
75
|
+
let finished = false;
|
|
76
|
+
let failure = null;
|
|
77
|
+
const queue = [];
|
|
78
|
+
let waiting = null;
|
|
79
|
+
let waitingReject = null;
|
|
80
|
+
let headersResolved = false;
|
|
81
|
+
const pump = () => {
|
|
82
|
+
const text = xhr.responseText;
|
|
83
|
+
if (text.length <= consumed) return;
|
|
84
|
+
const chunk = text.slice(consumed);
|
|
85
|
+
consumed = text.length;
|
|
86
|
+
if (waiting) {
|
|
87
|
+
const resolve = waiting;
|
|
88
|
+
waiting = null;
|
|
89
|
+
waitingReject = null;
|
|
90
|
+
resolve({ done: false, value: chunk });
|
|
91
|
+
} else {
|
|
92
|
+
queue.push(chunk);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const finish = (err) => {
|
|
96
|
+
if (finished) return;
|
|
97
|
+
finished = true;
|
|
98
|
+
failure = err ?? null;
|
|
99
|
+
if (waiting) {
|
|
100
|
+
const resolve = waiting;
|
|
101
|
+
const reject = waitingReject;
|
|
102
|
+
waiting = null;
|
|
103
|
+
waitingReject = null;
|
|
104
|
+
if (err && reject) reject(err);
|
|
105
|
+
else resolve({ done: true });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const reader = {
|
|
109
|
+
read() {
|
|
110
|
+
if (queue.length > 0) {
|
|
111
|
+
return Promise.resolve({ done: false, value: queue.shift() });
|
|
112
|
+
}
|
|
113
|
+
if (finished) {
|
|
114
|
+
return failure ? Promise.reject(failure) : Promise.resolve({ done: true });
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
waiting = resolve;
|
|
118
|
+
waitingReject = reject;
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
cancel() {
|
|
122
|
+
finish();
|
|
123
|
+
xhr.abort();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const resolveHeaders = () => {
|
|
127
|
+
if (headersResolved) return;
|
|
128
|
+
headersResolved = true;
|
|
129
|
+
const ok = xhr.status >= 200 && xhr.status < 300;
|
|
130
|
+
resolveStream({
|
|
131
|
+
status: xhr.status,
|
|
132
|
+
ok,
|
|
133
|
+
getHeader: (name) => xhr.getResponseHeader(name),
|
|
134
|
+
reader
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
xhr.onreadystatechange = () => {
|
|
138
|
+
if (xhr.readyState === 2) {
|
|
139
|
+
resolveHeaders();
|
|
140
|
+
} else if (xhr.readyState === 3) {
|
|
141
|
+
resolveHeaders();
|
|
142
|
+
pump();
|
|
143
|
+
} else if (xhr.readyState === 4) {
|
|
144
|
+
resolveHeaders();
|
|
145
|
+
pump();
|
|
146
|
+
finish();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
xhr.onerror = () => {
|
|
150
|
+
const err = new Error("Network request failed");
|
|
151
|
+
if (!headersResolved) rejectStream(err);
|
|
152
|
+
finish(err);
|
|
153
|
+
};
|
|
154
|
+
xhr.ontimeout = () => {
|
|
155
|
+
const err = new Error("Network request timed out");
|
|
156
|
+
if (!headersResolved) rejectStream(err);
|
|
157
|
+
finish(err);
|
|
158
|
+
};
|
|
159
|
+
if (opts.signal) {
|
|
160
|
+
if (opts.signal.aborted) {
|
|
161
|
+
xhr.abort();
|
|
162
|
+
finish();
|
|
163
|
+
} else {
|
|
164
|
+
opts.signal.addEventListener("abort", () => {
|
|
165
|
+
finish();
|
|
166
|
+
xhr.abort();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
xhr.send(opts.body);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async function openSSEStream(opts) {
|
|
174
|
+
if (!supportsStreamingFetch()) {
|
|
175
|
+
return xhrStream(opts);
|
|
176
|
+
}
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
if (opts.signal) {
|
|
179
|
+
if (opts.signal.aborted) controller.abort();
|
|
180
|
+
else opts.signal.addEventListener("abort", () => controller.abort());
|
|
181
|
+
}
|
|
182
|
+
const response = await fetch(opts.url, {
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers: opts.headers,
|
|
185
|
+
body: opts.body,
|
|
186
|
+
signal: controller.signal
|
|
187
|
+
});
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const errorText = await response.text().catch(() => "Stream failed");
|
|
190
|
+
return {
|
|
191
|
+
status: response.status,
|
|
192
|
+
ok: false,
|
|
193
|
+
errorText,
|
|
194
|
+
getHeader: (name) => response.headers.get(name),
|
|
195
|
+
reader: { read: async () => ({ done: true }), cancel: () => controller.abort() }
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (!response.body) {
|
|
199
|
+
const text = await response.text();
|
|
200
|
+
let handed = false;
|
|
201
|
+
return {
|
|
202
|
+
status: response.status,
|
|
203
|
+
ok: true,
|
|
204
|
+
getHeader: (name) => response.headers.get(name),
|
|
205
|
+
reader: {
|
|
206
|
+
read: async () => handed ? { done: true } : (handed = true, { done: false, value: text }),
|
|
207
|
+
cancel: () => controller.abort()
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
status: response.status,
|
|
213
|
+
ok: true,
|
|
214
|
+
getHeader: (name) => response.headers.get(name),
|
|
215
|
+
reader: fetchReader(response, controller)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/hooks/useChat.ts
|
|
45
220
|
function isErrorToolContent(content) {
|
|
46
221
|
if (typeof content !== "string" || !content.trim().startsWith("{")) return false;
|
|
47
222
|
try {
|
|
@@ -108,7 +283,7 @@ function normalizePendingInterrupt(pending) {
|
|
|
108
283
|
return null;
|
|
109
284
|
}
|
|
110
285
|
function useChat(options = {}) {
|
|
111
|
-
const { defaultAgentId, fetchWithAuth } = usePersonaContext();
|
|
286
|
+
const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken } = usePersonaContext();
|
|
112
287
|
const agentId = options.agentId || defaultAgentId;
|
|
113
288
|
const threadId = options.threadId;
|
|
114
289
|
const [messages, setMessages] = useState(options.initialMessages || []);
|
|
@@ -215,16 +390,25 @@ function useChat(options = {}) {
|
|
|
215
390
|
setInterrupt(null);
|
|
216
391
|
const controller = new AbortController();
|
|
217
392
|
abortControllerRef.current = controller;
|
|
393
|
+
const finalizeReasoning = () => {
|
|
394
|
+
setMessages(
|
|
395
|
+
(prev) => prev.map(
|
|
396
|
+
(m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m
|
|
397
|
+
)
|
|
398
|
+
);
|
|
399
|
+
};
|
|
218
400
|
try {
|
|
219
|
-
const payloadMessages = nextMessages.map((m) => ({
|
|
401
|
+
const payloadMessages = nextMessages.filter((m) => m.role !== "reasoning").map((m) => ({
|
|
220
402
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
221
403
|
content: m.content
|
|
222
404
|
}));
|
|
223
405
|
const resolvedThreadId = await (overrideOptions?.threadId ?? options.threadId);
|
|
224
|
-
const
|
|
225
|
-
|
|
406
|
+
const token = getAuthToken ? await getAuthToken() : null;
|
|
407
|
+
const stream = await openSSEStream({
|
|
408
|
+
url: `${baseUrl}/chat`,
|
|
226
409
|
headers: {
|
|
227
|
-
"Content-Type": "application/json"
|
|
410
|
+
"Content-Type": "application/json",
|
|
411
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
228
412
|
},
|
|
229
413
|
body: JSON.stringify({
|
|
230
414
|
agentId: targetAgentId,
|
|
@@ -234,19 +418,15 @@ function useChat(options = {}) {
|
|
|
234
418
|
}),
|
|
235
419
|
signal: controller.signal
|
|
236
420
|
});
|
|
237
|
-
if (!
|
|
238
|
-
|
|
239
|
-
throw new Error(`Chat error (${response.status}): ${errText}`);
|
|
240
|
-
}
|
|
241
|
-
if (!response.body) {
|
|
242
|
-
throw new Error("No response body received for streaming.");
|
|
421
|
+
if (!stream.ok) {
|
|
422
|
+
throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
243
423
|
}
|
|
244
|
-
const reader =
|
|
245
|
-
const decoder = new TextDecoder("utf-8");
|
|
424
|
+
const reader = stream.reader;
|
|
246
425
|
let buffer = "";
|
|
247
426
|
let accumulatedText = "";
|
|
248
|
-
let accumulatedReasoning = "";
|
|
249
427
|
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
428
|
+
let activeReasoningId = null;
|
|
429
|
+
const reasoningById = /* @__PURE__ */ new Map();
|
|
250
430
|
const patchAssistant = (patch) => {
|
|
251
431
|
setMessages(
|
|
252
432
|
(prev) => prev.map(
|
|
@@ -254,10 +434,26 @@ function useChat(options = {}) {
|
|
|
254
434
|
)
|
|
255
435
|
);
|
|
256
436
|
};
|
|
437
|
+
const insertReasoningMessage = (id) => {
|
|
438
|
+
const msg = {
|
|
439
|
+
id,
|
|
440
|
+
role: "reasoning",
|
|
441
|
+
content: "",
|
|
442
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
443
|
+
isStreaming: true
|
|
444
|
+
};
|
|
445
|
+
setMessages((prev) => {
|
|
446
|
+
const idx = prev.findIndex((m) => m.id === assistantMessageId);
|
|
447
|
+
if (idx === -1) return [...prev, msg];
|
|
448
|
+
const next = [...prev];
|
|
449
|
+
next.splice(idx, 0, msg);
|
|
450
|
+
return next;
|
|
451
|
+
});
|
|
452
|
+
};
|
|
257
453
|
while (true) {
|
|
258
454
|
const { done, value } = await reader.read();
|
|
259
455
|
if (done) break;
|
|
260
|
-
buffer +=
|
|
456
|
+
buffer += value ?? "";
|
|
261
457
|
const lines = buffer.split("\n");
|
|
262
458
|
buffer = lines.pop() ?? "";
|
|
263
459
|
for (const line of lines) {
|
|
@@ -297,11 +493,33 @@ function useChat(options = {}) {
|
|
|
297
493
|
} else if (event.type === "STATE_SNAPSHOT") {
|
|
298
494
|
setFiles(normalizeWorkspaceFiles(event.snapshot.files));
|
|
299
495
|
setTodos(event.snapshot.todos);
|
|
496
|
+
} else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
|
|
497
|
+
activeReasoningId = event.messageId;
|
|
498
|
+
reasoningById.set(event.messageId, { content: "" });
|
|
499
|
+
insertReasoningMessage(event.messageId);
|
|
300
500
|
} else if (event.type === "REASONING_MESSAGE_CONTENT") {
|
|
301
|
-
|
|
302
|
-
|
|
501
|
+
let rid = event.messageId || activeReasoningId || "";
|
|
502
|
+
if (!rid || !reasoningById.has(rid)) {
|
|
503
|
+
rid = rid || `reasoning-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
504
|
+
activeReasoningId = rid;
|
|
505
|
+
reasoningById.set(rid, { content: "" });
|
|
506
|
+
insertReasoningMessage(rid);
|
|
507
|
+
}
|
|
508
|
+
const entry = reasoningById.get(rid);
|
|
509
|
+
entry.content += event.delta;
|
|
510
|
+
setMessages(
|
|
511
|
+
(prev) => prev.map(
|
|
512
|
+
(m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
|
|
513
|
+
)
|
|
514
|
+
);
|
|
303
515
|
} else if (event.type === "REASONING_END") {
|
|
304
|
-
|
|
516
|
+
const rid = activeReasoningId;
|
|
517
|
+
activeReasoningId = null;
|
|
518
|
+
if (rid) {
|
|
519
|
+
setMessages(
|
|
520
|
+
(prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m)
|
|
521
|
+
);
|
|
522
|
+
}
|
|
305
523
|
} else if (event.type === "CUSTOM") {
|
|
306
524
|
if (event.name === "hitl_request") {
|
|
307
525
|
const value2 = event.value;
|
|
@@ -337,8 +555,7 @@ function useChat(options = {}) {
|
|
|
337
555
|
content: accumulatedText,
|
|
338
556
|
createdAt: /* @__PURE__ */ new Date(),
|
|
339
557
|
isStreaming: false,
|
|
340
|
-
toolCalls: Array.from(toolCallsMap.values())
|
|
341
|
-
...accumulatedReasoning ? { reasoning: accumulatedReasoning, isReasoning: false } : {}
|
|
558
|
+
toolCalls: Array.from(toolCallsMap.values())
|
|
342
559
|
};
|
|
343
560
|
setMessages(
|
|
344
561
|
(prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg)
|
|
@@ -368,9 +585,10 @@ function useChat(options = {}) {
|
|
|
368
585
|
} finally {
|
|
369
586
|
setIsStreaming(false);
|
|
370
587
|
abortControllerRef.current = null;
|
|
588
|
+
finalizeReasoning();
|
|
371
589
|
}
|
|
372
590
|
},
|
|
373
|
-
[agentId,
|
|
591
|
+
[agentId, baseUrl, getAuthToken, input, isStreaming, messages, options]
|
|
374
592
|
);
|
|
375
593
|
const handleInputChange = useCallback((e) => {
|
|
376
594
|
setInput(e.target.value);
|
|
@@ -856,10 +1074,12 @@ function useMcpConnections(options = {}) {
|
|
|
856
1074
|
}
|
|
857
1075
|
|
|
858
1076
|
// src/index.ts
|
|
859
|
-
var VERSION = "0.
|
|
1077
|
+
var VERSION = "0.4.0";
|
|
860
1078
|
export {
|
|
861
1079
|
PersonaProvider,
|
|
862
1080
|
VERSION,
|
|
1081
|
+
openSSEStream,
|
|
1082
|
+
supportsStreamingFetch,
|
|
863
1083
|
useAgents,
|
|
864
1084
|
useChat,
|
|
865
1085
|
useConnection,
|