@personaai/react 0.7.0 → 0.7.2

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
@@ -300,6 +300,14 @@ type PersonaVoiceEndReason = "client_closed" | "agent_ended" | "max_duration" |
300
300
  interface UseVoiceOptions {
301
301
  /** Falls back to `PersonaProvider`'s `defaultAgentId` if omitted. */
302
302
  agentId?: string;
303
+ /**
304
+ * Resume an existing conversation over voice instead of starting fresh.
305
+ * `start()` mints the session with this thread id (same value `useChat`'s
306
+ * `threadId` uses), so voice turns are persisted back into that thread's
307
+ * history — a later text message on the same thread sees them. Omit (or
308
+ * leave undefined) to start a fresh conversation.
309
+ */
310
+ threadId?: string;
303
311
  }
304
312
  interface UseVoiceResult {
305
313
  state: PersonaVoiceState;
@@ -566,6 +574,6 @@ declare function supportsStreamingFetch(): boolean;
566
574
  */
567
575
  declare function openSSEStream(opts: OpenSSEOptions): Promise<SSEStream>;
568
576
 
569
- declare const VERSION = "0.7.0";
577
+ declare const VERSION = "0.7.2";
570
578
 
571
579
  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 PersonaVoiceEndReason, type PersonaVoiceState, type PersonaVoiceToolCall, type PersonaVoiceTranscriptLine, type PersonaWorkspaceFile, type SSEReader, type SSEStream, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, type UseVoiceOptions, type UseVoiceResult, VERSION, openSSEStream, supportsStreamingFetch, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads, useVoice };
package/dist/index.d.ts CHANGED
@@ -300,6 +300,14 @@ type PersonaVoiceEndReason = "client_closed" | "agent_ended" | "max_duration" |
300
300
  interface UseVoiceOptions {
301
301
  /** Falls back to `PersonaProvider`'s `defaultAgentId` if omitted. */
302
302
  agentId?: string;
303
+ /**
304
+ * Resume an existing conversation over voice instead of starting fresh.
305
+ * `start()` mints the session with this thread id (same value `useChat`'s
306
+ * `threadId` uses), so voice turns are persisted back into that thread's
307
+ * history — a later text message on the same thread sees them. Omit (or
308
+ * leave undefined) to start a fresh conversation.
309
+ */
310
+ threadId?: string;
303
311
  }
304
312
  interface UseVoiceResult {
305
313
  state: PersonaVoiceState;
@@ -566,6 +574,6 @@ declare function supportsStreamingFetch(): boolean;
566
574
  */
567
575
  declare function openSSEStream(opts: OpenSSEOptions): Promise<SSEStream>;
568
576
 
569
- declare const VERSION = "0.7.0";
577
+ declare const VERSION = "0.7.2";
570
578
 
571
579
  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 PersonaVoiceEndReason, type PersonaVoiceState, type PersonaVoiceToolCall, type PersonaVoiceTranscriptLine, type PersonaWorkspaceFile, type SSEReader, type SSEStream, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, type UseVoiceOptions, type UseVoiceResult, VERSION, openSSEStream, supportsStreamingFetch, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads, useVoice };
package/dist/index.js CHANGED
@@ -1304,10 +1304,19 @@ function playerWorkletUrl() {
1304
1304
  }
1305
1305
 
1306
1306
  // src/hooks/useVoice.ts
1307
+ function mergeTranscriptText(prev, next) {
1308
+ if (!prev) return next;
1309
+ if (!next) return prev;
1310
+ if (next === prev || prev.endsWith(next)) return prev;
1311
+ if (next.startsWith(prev)) return next;
1312
+ const needSpace = !/\s$/.test(prev) && !/^\s/.test(next);
1313
+ return prev + (needSpace ? " " : "") + next;
1314
+ }
1307
1315
  function useVoice(options = {}) {
1308
1316
  const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
1309
1317
  const voiceLogger = useMemo3(() => logger.child("voice"), [logger]);
1310
1318
  const agentId = options.agentId || defaultAgentId;
1319
+ const threadId = options.threadId;
1311
1320
  const [state, setState] = useState4("idle");
1312
1321
  const [isMuted, setIsMuted] = useState4(false);
1313
1322
  const [transcript, setTranscript] = useState4(
@@ -1329,6 +1338,9 @@ function useVoice(options = {}) {
1329
1338
  const playerNodeRef = useRef2(null);
1330
1339
  const acceptedTurnSeqRef = useRef2(0);
1331
1340
  const mountedRef = useRef2(true);
1341
+ const lastFinalTurnRef = useRef2(
1342
+ null
1343
+ );
1332
1344
  useEffect4(() => {
1333
1345
  mountedRef.current = true;
1334
1346
  return () => {
@@ -1411,8 +1423,7 @@ function useVoice(options = {}) {
1411
1423
  (id, patch) => {
1412
1424
  setToolCalls((prev) => {
1413
1425
  const idx = prev.findIndex((t) => t.id === id);
1414
- if (idx === -1)
1415
- return [...prev, { id, status: "running", ...patch }];
1426
+ if (idx === -1) return [...prev, { id, status: "running", ...patch }];
1416
1427
  const next = [...prev];
1417
1428
  next[idx] = { ...next[idx], ...patch };
1418
1429
  return next;
@@ -1420,21 +1431,28 @@ function useVoice(options = {}) {
1420
1431
  },
1421
1432
  []
1422
1433
  );
1423
- const handleTranscript = useCallback4(
1424
- (value) => {
1425
- const { speaker, text, isFinal } = value;
1426
- if (isFinal) {
1427
- setPartial((prev) => prev?.speaker === speaker ? null : prev);
1428
- setTranscript((prev) => [
1429
- ...prev,
1430
- { id: `${speaker}-${prev.length}`, speaker, text }
1431
- ]);
1432
- } else {
1433
- setPartial({ id: "partial", speaker, text });
1434
+ const handleTranscript = useCallback4((value) => {
1435
+ const { speaker, text, isFinal, turnSeq } = value;
1436
+ if (!isFinal) {
1437
+ setPartial({ id: "partial", speaker, text });
1438
+ return;
1439
+ }
1440
+ setPartial((prev) => prev?.speaker === speaker ? null : prev);
1441
+ const last = lastFinalTurnRef.current;
1442
+ const sameUtterance = last && last.speaker === speaker && last.turnSeq === turnSeq;
1443
+ setTranscript((prev) => {
1444
+ const tail = prev[prev.length - 1];
1445
+ if (sameUtterance && tail?.speaker === speaker) {
1446
+ const merged = mergeTranscriptText(tail.text, text);
1447
+ if (merged === tail.text) return prev;
1448
+ return prev.map(
1449
+ (l, i) => i === prev.length - 1 ? { ...l, text: merged } : l
1450
+ );
1434
1451
  }
1435
- },
1436
- []
1437
- );
1452
+ return [...prev, { id: `${speaker}-${prev.length}`, speaker, text }];
1453
+ });
1454
+ lastFinalTurnRef.current = { speaker, turnSeq };
1455
+ }, []);
1438
1456
  const handleCustomEvent = useCallback4(
1439
1457
  (name, value) => {
1440
1458
  switch (name) {
@@ -1467,9 +1485,7 @@ function useVoice(options = {}) {
1467
1485
  }
1468
1486
  break;
1469
1487
  case "voice_transcript":
1470
- handleTranscript(
1471
- value
1472
- );
1488
+ handleTranscript(value);
1473
1489
  break;
1474
1490
  case "voice_interrupted":
1475
1491
  acceptedTurnSeqRef.current = value.turnSeq;
@@ -1542,7 +1558,9 @@ function useVoice(options = {}) {
1542
1558
  }
1543
1559
  case "RUN_ERROR":
1544
1560
  voiceLogger.warn("RUN_ERROR", { message: msg.message });
1545
- setError(new Error(msg.message || "The agent run failed."));
1561
+ setError(
1562
+ new Error(msg.message || "The agent run failed.")
1563
+ );
1546
1564
  setState("error");
1547
1565
  break;
1548
1566
  default:
@@ -1567,12 +1585,15 @@ function useVoice(options = {}) {
1567
1585
  setToolCalls([]);
1568
1586
  acceptedTurnSeqRef.current = 0;
1569
1587
  setState("connecting");
1570
- voiceLogger.debug("start() called", { agentId });
1588
+ voiceLogger.debug("start() called", { agentId, threadId });
1571
1589
  try {
1572
1590
  const res = await fetchWithAuth("/voice/sessions", {
1573
1591
  method: "POST",
1574
1592
  headers: { "Content-Type": "application/json" },
1575
- body: JSON.stringify({ agentId })
1593
+ body: JSON.stringify({
1594
+ agentId,
1595
+ ...threadId ? { threadId } : {}
1596
+ })
1576
1597
  });
1577
1598
  if (!res.ok) {
1578
1599
  throw new Error(`Failed to start voice session: ${res.statusText}`);
@@ -1607,7 +1628,7 @@ function useVoice(options = {}) {
1607
1628
  }
1608
1629
  teardownAudio();
1609
1630
  }
1610
- }, [agentId, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
1631
+ }, [agentId, threadId, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
1611
1632
  const mute = useCallback4((muted) => {
1612
1633
  setIsMuted(muted);
1613
1634
  streamRef.current?.getAudioTracks().forEach((track) => {
@@ -1867,7 +1888,7 @@ import {
1867
1888
  getLogLevel,
1868
1889
  isLevelEnabled
1869
1890
  } from "@personaai/logger";
1870
- var VERSION = "0.7.0";
1891
+ var VERSION = "0.7.2";
1871
1892
  export {
1872
1893
  PersonaProvider,
1873
1894
  VERSION,