@personaai/react 0.3.5 → 0.4.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 +191 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -2
- package/dist/index.d.ts +58 -2
- package/dist/index.js +189 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -415,6 +415,62 @@ declare function useMcpConnections(options?: UseMcpConnectionsOptions): {
|
|
|
415
415
|
refetch: () => Promise<PersonaMcpConnection[]>;
|
|
416
416
|
};
|
|
417
417
|
|
|
418
|
-
|
|
418
|
+
/**
|
|
419
|
+
* SSE transport for chat streams, with a React Native fallback.
|
|
420
|
+
*
|
|
421
|
+
* On the web, `fetch` gives a streaming `response.body` and we read it with a
|
|
422
|
+
* ReadableStream reader. React Native's `fetch` is the whatwg-fetch polyfill
|
|
423
|
+
* over XMLHttpRequest: `response.body` is `undefined`, and the promise only
|
|
424
|
+
* settles once the entire response has arrived. That means the check cannot be
|
|
425
|
+
* "call fetch, then see whether body exists" - by that point the whole stream
|
|
426
|
+
* has already been buffered and the chance to stream is gone. The transport has
|
|
427
|
+
* to be chosen *before* the request is made.
|
|
428
|
+
*
|
|
429
|
+
* XMLHttpRequest itself does stream everywhere React Native runs: it appends to
|
|
430
|
+
* `responseText` and fires `readyState === 3` (LOADING) on every chunk, which is
|
|
431
|
+
* all an SSE consumer needs. So the RN path issues the request over raw XHR and
|
|
432
|
+
* hands back the same reader interface the fetch path does, leaving callers
|
|
433
|
+
* unaware of which one they got.
|
|
434
|
+
*/
|
|
435
|
+
/** Minimal reader interface, mirroring the shape of a ReadableStream reader. */
|
|
436
|
+
interface SSEReader {
|
|
437
|
+
/** Resolves with the next decoded chunk, or `{ done: true }` at end of stream. */
|
|
438
|
+
read(): Promise<{
|
|
439
|
+
done: boolean;
|
|
440
|
+
value?: string;
|
|
441
|
+
}>;
|
|
442
|
+
/** Aborts the underlying request. */
|
|
443
|
+
cancel(): void;
|
|
444
|
+
}
|
|
445
|
+
interface SSEStream {
|
|
446
|
+
status: number;
|
|
447
|
+
ok: boolean;
|
|
448
|
+
getHeader(name: string): string | null;
|
|
449
|
+
/** Present only when `ok` is false, so callers can surface the server's message. */
|
|
450
|
+
errorText?: string;
|
|
451
|
+
reader: SSEReader;
|
|
452
|
+
}
|
|
453
|
+
interface OpenSSEOptions {
|
|
454
|
+
url: string;
|
|
455
|
+
headers: Record<string, string>;
|
|
456
|
+
body: string;
|
|
457
|
+
signal?: AbortSignal;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Whether `fetch` on this platform yields a streaming body.
|
|
461
|
+
*
|
|
462
|
+
* `navigator.product === 'ReactNative'` is the long-standing marker React
|
|
463
|
+
* Native sets, and is checked rather than feature-detecting `ReadableStream`:
|
|
464
|
+
* newer RN versions do expose a global `ReadableStream` while still leaving
|
|
465
|
+
* `response.body` undefined, so the presence of the type says nothing about
|
|
466
|
+
* whether fetch will populate it.
|
|
467
|
+
*/
|
|
468
|
+
declare function supportsStreamingFetch(): boolean;
|
|
469
|
+
/**
|
|
470
|
+
* Opens an SSE stream, using whichever transport this platform can stream over.
|
|
471
|
+
*/
|
|
472
|
+
declare function openSSEStream(opts: OpenSSEOptions): Promise<SSEStream>;
|
|
473
|
+
|
|
474
|
+
declare const VERSION = "0.4.0";
|
|
419
475
|
|
|
420
|
-
export { type PersonaAgentSummary, type PersonaClarificationQuestion, type PersonaFileItem, type PersonaHealthInfo, type PersonaHitlActionRequest, type PersonaInterrupt, type PersonaMcpConnection, type PersonaMemoryAgentGroup, type PersonaMemoryFile, type PersonaMemoryList, type PersonaMessage, type PersonaPresentedFile, PersonaProvider, type PersonaProviderProps, type PersonaResumeValue, type PersonaRole, type PersonaStreamingEvent, type PersonaSubagentActivityEntry, type PersonaThread, type PersonaTodo, type PersonaToolCall, type PersonaWorkspaceFile, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, VERSION, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads };
|
|
476
|
+
export { type OpenSSEOptions, type PersonaAgentSummary, type PersonaClarificationQuestion, type PersonaFileItem, type PersonaHealthInfo, type PersonaHitlActionRequest, type PersonaInterrupt, type PersonaMcpConnection, type PersonaMemoryAgentGroup, type PersonaMemoryFile, type PersonaMemoryList, type PersonaMessage, type PersonaPresentedFile, PersonaProvider, type PersonaProviderProps, type PersonaResumeValue, type PersonaRole, type PersonaStreamingEvent, type PersonaSubagentActivityEntry, type PersonaThread, type PersonaTodo, type PersonaToolCall, type PersonaWorkspaceFile, type SSEReader, type SSEStream, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, VERSION, openSSEStream, supportsStreamingFetch, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads };
|
package/dist/index.d.ts
CHANGED
|
@@ -415,6 +415,62 @@ declare function useMcpConnections(options?: UseMcpConnectionsOptions): {
|
|
|
415
415
|
refetch: () => Promise<PersonaMcpConnection[]>;
|
|
416
416
|
};
|
|
417
417
|
|
|
418
|
-
|
|
418
|
+
/**
|
|
419
|
+
* SSE transport for chat streams, with a React Native fallback.
|
|
420
|
+
*
|
|
421
|
+
* On the web, `fetch` gives a streaming `response.body` and we read it with a
|
|
422
|
+
* ReadableStream reader. React Native's `fetch` is the whatwg-fetch polyfill
|
|
423
|
+
* over XMLHttpRequest: `response.body` is `undefined`, and the promise only
|
|
424
|
+
* settles once the entire response has arrived. That means the check cannot be
|
|
425
|
+
* "call fetch, then see whether body exists" - by that point the whole stream
|
|
426
|
+
* has already been buffered and the chance to stream is gone. The transport has
|
|
427
|
+
* to be chosen *before* the request is made.
|
|
428
|
+
*
|
|
429
|
+
* XMLHttpRequest itself does stream everywhere React Native runs: it appends to
|
|
430
|
+
* `responseText` and fires `readyState === 3` (LOADING) on every chunk, which is
|
|
431
|
+
* all an SSE consumer needs. So the RN path issues the request over raw XHR and
|
|
432
|
+
* hands back the same reader interface the fetch path does, leaving callers
|
|
433
|
+
* unaware of which one they got.
|
|
434
|
+
*/
|
|
435
|
+
/** Minimal reader interface, mirroring the shape of a ReadableStream reader. */
|
|
436
|
+
interface SSEReader {
|
|
437
|
+
/** Resolves with the next decoded chunk, or `{ done: true }` at end of stream. */
|
|
438
|
+
read(): Promise<{
|
|
439
|
+
done: boolean;
|
|
440
|
+
value?: string;
|
|
441
|
+
}>;
|
|
442
|
+
/** Aborts the underlying request. */
|
|
443
|
+
cancel(): void;
|
|
444
|
+
}
|
|
445
|
+
interface SSEStream {
|
|
446
|
+
status: number;
|
|
447
|
+
ok: boolean;
|
|
448
|
+
getHeader(name: string): string | null;
|
|
449
|
+
/** Present only when `ok` is false, so callers can surface the server's message. */
|
|
450
|
+
errorText?: string;
|
|
451
|
+
reader: SSEReader;
|
|
452
|
+
}
|
|
453
|
+
interface OpenSSEOptions {
|
|
454
|
+
url: string;
|
|
455
|
+
headers: Record<string, string>;
|
|
456
|
+
body: string;
|
|
457
|
+
signal?: AbortSignal;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Whether `fetch` on this platform yields a streaming body.
|
|
461
|
+
*
|
|
462
|
+
* `navigator.product === 'ReactNative'` is the long-standing marker React
|
|
463
|
+
* Native sets, and is checked rather than feature-detecting `ReadableStream`:
|
|
464
|
+
* newer RN versions do expose a global `ReadableStream` while still leaving
|
|
465
|
+
* `response.body` undefined, so the presence of the type says nothing about
|
|
466
|
+
* whether fetch will populate it.
|
|
467
|
+
*/
|
|
468
|
+
declare function supportsStreamingFetch(): boolean;
|
|
469
|
+
/**
|
|
470
|
+
* Opens an SSE stream, using whichever transport this platform can stream over.
|
|
471
|
+
*/
|
|
472
|
+
declare function openSSEStream(opts: OpenSSEOptions): Promise<SSEStream>;
|
|
473
|
+
|
|
474
|
+
declare const VERSION = "0.4.0";
|
|
419
475
|
|
|
420
|
-
export { type PersonaAgentSummary, type PersonaClarificationQuestion, type PersonaFileItem, type PersonaHealthInfo, type PersonaHitlActionRequest, type PersonaInterrupt, type PersonaMcpConnection, type PersonaMemoryAgentGroup, type PersonaMemoryFile, type PersonaMemoryList, type PersonaMessage, type PersonaPresentedFile, PersonaProvider, type PersonaProviderProps, type PersonaResumeValue, type PersonaRole, type PersonaStreamingEvent, type PersonaSubagentActivityEntry, type PersonaThread, type PersonaTodo, type PersonaToolCall, type PersonaWorkspaceFile, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, VERSION, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads };
|
|
476
|
+
export { type OpenSSEOptions, type PersonaAgentSummary, type PersonaClarificationQuestion, type PersonaFileItem, type PersonaHealthInfo, type PersonaHitlActionRequest, type PersonaInterrupt, type PersonaMcpConnection, type PersonaMemoryAgentGroup, type PersonaMemoryFile, type PersonaMemoryList, type PersonaMessage, type PersonaPresentedFile, PersonaProvider, type PersonaProviderProps, type PersonaResumeValue, type PersonaRole, type PersonaStreamingEvent, type PersonaSubagentActivityEntry, type PersonaThread, type PersonaTodo, type PersonaToolCall, type PersonaWorkspaceFile, type SSEReader, type SSEStream, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, VERSION, openSSEStream, supportsStreamingFetch, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads };
|
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 || []);
|
|
@@ -221,10 +396,12 @@ function useChat(options = {}) {
|
|
|
221
396
|
content: m.content
|
|
222
397
|
}));
|
|
223
398
|
const resolvedThreadId = await (overrideOptions?.threadId ?? options.threadId);
|
|
224
|
-
const
|
|
225
|
-
|
|
399
|
+
const token = getAuthToken ? await getAuthToken() : null;
|
|
400
|
+
const stream = await openSSEStream({
|
|
401
|
+
url: `${baseUrl}/chat`,
|
|
226
402
|
headers: {
|
|
227
|
-
"Content-Type": "application/json"
|
|
403
|
+
"Content-Type": "application/json",
|
|
404
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
228
405
|
},
|
|
229
406
|
body: JSON.stringify({
|
|
230
407
|
agentId: targetAgentId,
|
|
@@ -234,15 +411,10 @@ function useChat(options = {}) {
|
|
|
234
411
|
}),
|
|
235
412
|
signal: controller.signal
|
|
236
413
|
});
|
|
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.");
|
|
414
|
+
if (!stream.ok) {
|
|
415
|
+
throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
243
416
|
}
|
|
244
|
-
const reader =
|
|
245
|
-
const decoder = new TextDecoder("utf-8");
|
|
417
|
+
const reader = stream.reader;
|
|
246
418
|
let buffer = "";
|
|
247
419
|
let accumulatedText = "";
|
|
248
420
|
let accumulatedReasoning = "";
|
|
@@ -257,7 +429,7 @@ function useChat(options = {}) {
|
|
|
257
429
|
while (true) {
|
|
258
430
|
const { done, value } = await reader.read();
|
|
259
431
|
if (done) break;
|
|
260
|
-
buffer +=
|
|
432
|
+
buffer += value ?? "";
|
|
261
433
|
const lines = buffer.split("\n");
|
|
262
434
|
buffer = lines.pop() ?? "";
|
|
263
435
|
for (const line of lines) {
|
|
@@ -370,7 +542,7 @@ function useChat(options = {}) {
|
|
|
370
542
|
abortControllerRef.current = null;
|
|
371
543
|
}
|
|
372
544
|
},
|
|
373
|
-
[agentId,
|
|
545
|
+
[agentId, baseUrl, getAuthToken, input, isStreaming, messages, options]
|
|
374
546
|
);
|
|
375
547
|
const handleInputChange = useCallback((e) => {
|
|
376
548
|
setInput(e.target.value);
|
|
@@ -856,10 +1028,12 @@ function useMcpConnections(options = {}) {
|
|
|
856
1028
|
}
|
|
857
1029
|
|
|
858
1030
|
// src/index.ts
|
|
859
|
-
var VERSION = "0.
|
|
1031
|
+
var VERSION = "0.4.0";
|
|
860
1032
|
export {
|
|
861
1033
|
PersonaProvider,
|
|
862
1034
|
VERSION,
|
|
1035
|
+
openSSEStream,
|
|
1036
|
+
supportsStreamingFetch,
|
|
863
1037
|
useAgents,
|
|
864
1038
|
useChat,
|
|
865
1039
|
useConnection,
|