@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.d.cts CHANGED
@@ -415,6 +415,62 @@ declare function useMcpConnections(options?: UseMcpConnectionsOptions): {
415
415
  refetch: () => Promise<PersonaMcpConnection[]>;
416
416
  };
417
417
 
418
- declare const VERSION = "0.3.5";
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
- declare const VERSION = "0.3.5";
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 response = await fetchWithAuth("/chat", {
225
- method: "POST",
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 (!response.ok) {
238
- const errText = await response.text().catch(() => "Stream failed");
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 = response.body.getReader();
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 += decoder.decode(value, { stream: true });
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, fetchWithAuth, input, isStreaming, messages, options]
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.3.5";
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,