@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 CHANGED
@@ -22,6 +22,8 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  PersonaProvider: () => PersonaProvider,
24
24
  VERSION: () => VERSION,
25
+ openSSEStream: () => openSSEStream,
26
+ supportsStreamingFetch: () => supportsStreamingFetch,
25
27
  useAgents: () => useAgents,
26
28
  useChat: () => useChat,
27
29
  useConnection: () => useConnection,
@@ -77,6 +79,181 @@ function usePersonaContext() {
77
79
 
78
80
  // src/hooks/useChat.ts
79
81
  var import_react2 = require("react");
82
+
83
+ // src/streaming.ts
84
+ function supportsStreamingFetch() {
85
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
86
+ return false;
87
+ }
88
+ return typeof fetch !== "undefined" && typeof ReadableStream !== "undefined";
89
+ }
90
+ function fetchReader(response, controller) {
91
+ const reader = response.body.getReader();
92
+ const decoder = new TextDecoder("utf-8");
93
+ return {
94
+ async read() {
95
+ const { done, value } = await reader.read();
96
+ if (done) return { done: true };
97
+ return { done: false, value: decoder.decode(value, { stream: true }) };
98
+ },
99
+ cancel() {
100
+ controller.abort();
101
+ void reader.cancel().catch(() => {
102
+ });
103
+ }
104
+ };
105
+ }
106
+ function xhrStream(opts) {
107
+ return new Promise((resolveStream, rejectStream) => {
108
+ const xhr = new XMLHttpRequest();
109
+ xhr.open("POST", opts.url);
110
+ for (const [key, val] of Object.entries(opts.headers)) xhr.setRequestHeader(key, val);
111
+ let consumed = 0;
112
+ let finished = false;
113
+ let failure = null;
114
+ const queue = [];
115
+ let waiting = null;
116
+ let waitingReject = null;
117
+ let headersResolved = false;
118
+ const pump = () => {
119
+ const text = xhr.responseText;
120
+ if (text.length <= consumed) return;
121
+ const chunk = text.slice(consumed);
122
+ consumed = text.length;
123
+ if (waiting) {
124
+ const resolve = waiting;
125
+ waiting = null;
126
+ waitingReject = null;
127
+ resolve({ done: false, value: chunk });
128
+ } else {
129
+ queue.push(chunk);
130
+ }
131
+ };
132
+ const finish = (err) => {
133
+ if (finished) return;
134
+ finished = true;
135
+ failure = err ?? null;
136
+ if (waiting) {
137
+ const resolve = waiting;
138
+ const reject = waitingReject;
139
+ waiting = null;
140
+ waitingReject = null;
141
+ if (err && reject) reject(err);
142
+ else resolve({ done: true });
143
+ }
144
+ };
145
+ const reader = {
146
+ read() {
147
+ if (queue.length > 0) {
148
+ return Promise.resolve({ done: false, value: queue.shift() });
149
+ }
150
+ if (finished) {
151
+ return failure ? Promise.reject(failure) : Promise.resolve({ done: true });
152
+ }
153
+ return new Promise((resolve, reject) => {
154
+ waiting = resolve;
155
+ waitingReject = reject;
156
+ });
157
+ },
158
+ cancel() {
159
+ finish();
160
+ xhr.abort();
161
+ }
162
+ };
163
+ const resolveHeaders = () => {
164
+ if (headersResolved) return;
165
+ headersResolved = true;
166
+ const ok = xhr.status >= 200 && xhr.status < 300;
167
+ resolveStream({
168
+ status: xhr.status,
169
+ ok,
170
+ getHeader: (name) => xhr.getResponseHeader(name),
171
+ reader
172
+ });
173
+ };
174
+ xhr.onreadystatechange = () => {
175
+ if (xhr.readyState === 2) {
176
+ resolveHeaders();
177
+ } else if (xhr.readyState === 3) {
178
+ resolveHeaders();
179
+ pump();
180
+ } else if (xhr.readyState === 4) {
181
+ resolveHeaders();
182
+ pump();
183
+ finish();
184
+ }
185
+ };
186
+ xhr.onerror = () => {
187
+ const err = new Error("Network request failed");
188
+ if (!headersResolved) rejectStream(err);
189
+ finish(err);
190
+ };
191
+ xhr.ontimeout = () => {
192
+ const err = new Error("Network request timed out");
193
+ if (!headersResolved) rejectStream(err);
194
+ finish(err);
195
+ };
196
+ if (opts.signal) {
197
+ if (opts.signal.aborted) {
198
+ xhr.abort();
199
+ finish();
200
+ } else {
201
+ opts.signal.addEventListener("abort", () => {
202
+ finish();
203
+ xhr.abort();
204
+ });
205
+ }
206
+ }
207
+ xhr.send(opts.body);
208
+ });
209
+ }
210
+ async function openSSEStream(opts) {
211
+ if (!supportsStreamingFetch()) {
212
+ return xhrStream(opts);
213
+ }
214
+ const controller = new AbortController();
215
+ if (opts.signal) {
216
+ if (opts.signal.aborted) controller.abort();
217
+ else opts.signal.addEventListener("abort", () => controller.abort());
218
+ }
219
+ const response = await fetch(opts.url, {
220
+ method: "POST",
221
+ headers: opts.headers,
222
+ body: opts.body,
223
+ signal: controller.signal
224
+ });
225
+ if (!response.ok) {
226
+ const errorText = await response.text().catch(() => "Stream failed");
227
+ return {
228
+ status: response.status,
229
+ ok: false,
230
+ errorText,
231
+ getHeader: (name) => response.headers.get(name),
232
+ reader: { read: async () => ({ done: true }), cancel: () => controller.abort() }
233
+ };
234
+ }
235
+ if (!response.body) {
236
+ const text = await response.text();
237
+ let handed = false;
238
+ return {
239
+ status: response.status,
240
+ ok: true,
241
+ getHeader: (name) => response.headers.get(name),
242
+ reader: {
243
+ read: async () => handed ? { done: true } : (handed = true, { done: false, value: text }),
244
+ cancel: () => controller.abort()
245
+ }
246
+ };
247
+ }
248
+ return {
249
+ status: response.status,
250
+ ok: true,
251
+ getHeader: (name) => response.headers.get(name),
252
+ reader: fetchReader(response, controller)
253
+ };
254
+ }
255
+
256
+ // src/hooks/useChat.ts
80
257
  function isErrorToolContent(content) {
81
258
  if (typeof content !== "string" || !content.trim().startsWith("{")) return false;
82
259
  try {
@@ -143,7 +320,7 @@ function normalizePendingInterrupt(pending) {
143
320
  return null;
144
321
  }
145
322
  function useChat(options = {}) {
146
- const { defaultAgentId, fetchWithAuth } = usePersonaContext();
323
+ const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken } = usePersonaContext();
147
324
  const agentId = options.agentId || defaultAgentId;
148
325
  const threadId = options.threadId;
149
326
  const [messages, setMessages] = (0, import_react2.useState)(options.initialMessages || []);
@@ -250,16 +427,25 @@ function useChat(options = {}) {
250
427
  setInterrupt(null);
251
428
  const controller = new AbortController();
252
429
  abortControllerRef.current = controller;
430
+ const finalizeReasoning = () => {
431
+ setMessages(
432
+ (prev) => prev.map(
433
+ (m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m
434
+ )
435
+ );
436
+ };
253
437
  try {
254
- const payloadMessages = nextMessages.map((m) => ({
438
+ const payloadMessages = nextMessages.filter((m) => m.role !== "reasoning").map((m) => ({
255
439
  role: m.role === "assistant" ? "assistant" : "user",
256
440
  content: m.content
257
441
  }));
258
442
  const resolvedThreadId = await (overrideOptions?.threadId ?? options.threadId);
259
- const response = await fetchWithAuth("/chat", {
260
- method: "POST",
443
+ const token = getAuthToken ? await getAuthToken() : null;
444
+ const stream = await openSSEStream({
445
+ url: `${baseUrl}/chat`,
261
446
  headers: {
262
- "Content-Type": "application/json"
447
+ "Content-Type": "application/json",
448
+ ...token ? { Authorization: `Bearer ${token}` } : {}
263
449
  },
264
450
  body: JSON.stringify({
265
451
  agentId: targetAgentId,
@@ -269,19 +455,15 @@ function useChat(options = {}) {
269
455
  }),
270
456
  signal: controller.signal
271
457
  });
272
- if (!response.ok) {
273
- const errText = await response.text().catch(() => "Stream failed");
274
- throw new Error(`Chat error (${response.status}): ${errText}`);
275
- }
276
- if (!response.body) {
277
- throw new Error("No response body received for streaming.");
458
+ if (!stream.ok) {
459
+ throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
278
460
  }
279
- const reader = response.body.getReader();
280
- const decoder = new TextDecoder("utf-8");
461
+ const reader = stream.reader;
281
462
  let buffer = "";
282
463
  let accumulatedText = "";
283
- let accumulatedReasoning = "";
284
464
  const toolCallsMap = /* @__PURE__ */ new Map();
465
+ let activeReasoningId = null;
466
+ const reasoningById = /* @__PURE__ */ new Map();
285
467
  const patchAssistant = (patch) => {
286
468
  setMessages(
287
469
  (prev) => prev.map(
@@ -289,10 +471,26 @@ function useChat(options = {}) {
289
471
  )
290
472
  );
291
473
  };
474
+ const insertReasoningMessage = (id) => {
475
+ const msg = {
476
+ id,
477
+ role: "reasoning",
478
+ content: "",
479
+ createdAt: /* @__PURE__ */ new Date(),
480
+ isStreaming: true
481
+ };
482
+ setMessages((prev) => {
483
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
484
+ if (idx === -1) return [...prev, msg];
485
+ const next = [...prev];
486
+ next.splice(idx, 0, msg);
487
+ return next;
488
+ });
489
+ };
292
490
  while (true) {
293
491
  const { done, value } = await reader.read();
294
492
  if (done) break;
295
- buffer += decoder.decode(value, { stream: true });
493
+ buffer += value ?? "";
296
494
  const lines = buffer.split("\n");
297
495
  buffer = lines.pop() ?? "";
298
496
  for (const line of lines) {
@@ -332,11 +530,33 @@ function useChat(options = {}) {
332
530
  } else if (event.type === "STATE_SNAPSHOT") {
333
531
  setFiles(normalizeWorkspaceFiles(event.snapshot.files));
334
532
  setTodos(event.snapshot.todos);
533
+ } else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
534
+ activeReasoningId = event.messageId;
535
+ reasoningById.set(event.messageId, { content: "" });
536
+ insertReasoningMessage(event.messageId);
335
537
  } else if (event.type === "REASONING_MESSAGE_CONTENT") {
336
- accumulatedReasoning += event.delta;
337
- patchAssistant({ reasoning: accumulatedReasoning, isReasoning: true });
538
+ let rid = event.messageId || activeReasoningId || "";
539
+ if (!rid || !reasoningById.has(rid)) {
540
+ rid = rid || `reasoning-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
541
+ activeReasoningId = rid;
542
+ reasoningById.set(rid, { content: "" });
543
+ insertReasoningMessage(rid);
544
+ }
545
+ const entry = reasoningById.get(rid);
546
+ entry.content += event.delta;
547
+ setMessages(
548
+ (prev) => prev.map(
549
+ (m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
550
+ )
551
+ );
338
552
  } else if (event.type === "REASONING_END") {
339
- patchAssistant({ isReasoning: false });
553
+ const rid = activeReasoningId;
554
+ activeReasoningId = null;
555
+ if (rid) {
556
+ setMessages(
557
+ (prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m)
558
+ );
559
+ }
340
560
  } else if (event.type === "CUSTOM") {
341
561
  if (event.name === "hitl_request") {
342
562
  const value2 = event.value;
@@ -372,8 +592,7 @@ function useChat(options = {}) {
372
592
  content: accumulatedText,
373
593
  createdAt: /* @__PURE__ */ new Date(),
374
594
  isStreaming: false,
375
- toolCalls: Array.from(toolCallsMap.values()),
376
- ...accumulatedReasoning ? { reasoning: accumulatedReasoning, isReasoning: false } : {}
595
+ toolCalls: Array.from(toolCallsMap.values())
377
596
  };
378
597
  setMessages(
379
598
  (prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg)
@@ -403,9 +622,10 @@ function useChat(options = {}) {
403
622
  } finally {
404
623
  setIsStreaming(false);
405
624
  abortControllerRef.current = null;
625
+ finalizeReasoning();
406
626
  }
407
627
  },
408
- [agentId, fetchWithAuth, input, isStreaming, messages, options]
628
+ [agentId, baseUrl, getAuthToken, input, isStreaming, messages, options]
409
629
  );
410
630
  const handleInputChange = (0, import_react2.useCallback)((e) => {
411
631
  setInput(e.target.value);
@@ -891,11 +1111,13 @@ function useMcpConnections(options = {}) {
891
1111
  }
892
1112
 
893
1113
  // src/index.ts
894
- var VERSION = "0.3.5";
1114
+ var VERSION = "0.4.0";
895
1115
  // Annotate the CommonJS export names for ESM import in node:
896
1116
  0 && (module.exports = {
897
1117
  PersonaProvider,
898
1118
  VERSION,
1119
+ openSSEStream,
1120
+ supportsStreamingFetch,
899
1121
  useAgents,
900
1122
  useChat,
901
1123
  useConnection,