@paymanai/payman-typescript-ask-sdk 4.0.9 → 4.0.13

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.
@@ -4,7 +4,9 @@ var react = require('react');
4
4
  var reactNativeMmkv = require('react-native-mmkv');
5
5
  var expoSpeechRecognition = require('expo-speech-recognition');
6
6
 
7
- // src/hooks/useChatV2.ts
7
+ var __defProp = Object.defineProperty;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
8
10
 
9
11
  // src/utils/generateId.ts
10
12
  function generateId() {
@@ -14,6 +16,95 @@ function generateId() {
14
16
  return v.toString(16);
15
17
  });
16
18
  }
19
+ var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
20
+ var chatStore = {
21
+ get(key) {
22
+ const raw = storage.getString(key);
23
+ if (!raw) return [];
24
+ try {
25
+ return JSON.parse(raw);
26
+ } catch {
27
+ return [];
28
+ }
29
+ },
30
+ set(key, messages) {
31
+ storage.set(key, JSON.stringify(messages));
32
+ },
33
+ delete(key) {
34
+ storage.delete(key);
35
+ }
36
+ };
37
+
38
+ // src/utils/activeStreamStore.ts
39
+ var streams = /* @__PURE__ */ new Map();
40
+ var activeStreamStore = {
41
+ has(key) {
42
+ return streams.has(key);
43
+ },
44
+ get(key) {
45
+ const entry = streams.get(key);
46
+ if (!entry) return null;
47
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
48
+ },
49
+ // Called before startStream — registers the controller and initial messages
50
+ start(key, abortController, initialMessages) {
51
+ const existing = streams.get(key);
52
+ streams.set(key, {
53
+ messages: initialMessages,
54
+ isWaiting: true,
55
+ abortController,
56
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
57
+ });
58
+ },
59
+ // Called by the stream on every event — applies the same updater pattern React uses
60
+ applyMessages(key, updater) {
61
+ const entry = streams.get(key);
62
+ if (!entry) return;
63
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
64
+ entry.messages = next;
65
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
66
+ },
67
+ setWaiting(key, waiting) {
68
+ const entry = streams.get(key);
69
+ if (!entry) return;
70
+ entry.isWaiting = waiting;
71
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
72
+ },
73
+ // Called when stream completes — persists to chatStore and cleans up
74
+ complete(key) {
75
+ const entry = streams.get(key);
76
+ if (!entry) return;
77
+ entry.isWaiting = false;
78
+ entry.listeners.forEach((l) => l(entry.messages, false));
79
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
80
+ if (toSave.length > 0) chatStore.set(key, toSave);
81
+ streams.delete(key);
82
+ },
83
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
84
+ subscribe(key, listener) {
85
+ const entry = streams.get(key);
86
+ if (!entry) return () => {
87
+ };
88
+ entry.listeners.add(listener);
89
+ return () => {
90
+ streams.get(key)?.listeners.delete(listener);
91
+ };
92
+ },
93
+ // Rename an entry — used when the server assigns a new session ID mid-stream
94
+ rename(oldKey, newKey) {
95
+ const entry = streams.get(oldKey);
96
+ if (!entry) return;
97
+ streams.set(newKey, entry);
98
+ streams.delete(oldKey);
99
+ },
100
+ // Explicit user cancel — aborts the controller and removes the entry
101
+ abort(key) {
102
+ const entry = streams.get(key);
103
+ if (!entry) return;
104
+ entry.abortController.abort();
105
+ streams.delete(key);
106
+ }
107
+ };
17
108
 
18
109
  // src/utils/streamingClient.native.ts
19
110
  function parseJSONBuffer(buffer) {
@@ -275,62 +366,28 @@ function normalizeEvent(event) {
275
366
  return event;
276
367
  }
277
368
  }
278
- function isUserActionPrompt(text) {
279
- return /\benter\b.+\b(code|otp)\b/i.test(text) || /\b(authorization|verification)\s+code\b/i.test(text) || /\bsent\s+to\s+your\b/i.test(text);
369
+ function isVerificationSchema(schema) {
370
+ const props = schema?.properties;
371
+ if (!props) return false;
372
+ const keys = Object.keys(props);
373
+ return keys.length === 1 && keys[0] === "verificationCode";
280
374
  }
281
- function getUserActionDisplayMessage(eventType, rawMessage) {
282
- const raw = rawMessage?.trim();
283
- const safeRaw = raw && !isUserActionPrompt(raw) ? raw : "";
284
- switch (eventType) {
285
- case "USER_ACTION_REQUIRED":
286
- return raw || "Waiting for verification...";
287
- case "USER_ACTION_SUCCESS":
288
- return safeRaw || "Verification approved";
289
- case "USER_ACTION_INVALID":
290
- return safeRaw || "Invalid code. Please try again.";
291
- case "USER_ACTION_REJECTED":
292
- return safeRaw || "Verification cancelled";
293
- case "USER_ACTION_EXPIRED":
294
- return safeRaw || "Verification expired";
295
- case "USER_ACTION_RESENT":
296
- return safeRaw || "Verification code resent";
297
- case "USER_ACTION_FAILED":
298
- return safeRaw || "Verification failed";
375
+ function classifyUserActionKind(action, schema) {
376
+ switch ((action || "").toLowerCase()) {
377
+ case "userverificationrequest":
378
+ return "verification";
379
+ case "usernotificationrequest":
380
+ return "notification";
381
+ case "userformrequest":
382
+ return isVerificationSchema(schema) ? "verification" : "form";
299
383
  default:
300
- return safeRaw || raw || eventType;
301
- }
302
- }
303
- function workingPhaseDetailForDisplay(raw) {
304
- const t = raw.trim();
305
- if (!t) return "";
306
- if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
307
- return "";
384
+ return isVerificationSchema(schema) ? "verification" : "form";
308
385
  }
309
- return t;
310
386
  }
311
387
  function getEventText(event, field) {
312
388
  const value = event[field];
313
389
  return typeof value === "string" ? value.trim() : "";
314
390
  }
315
- function shouldShowIntentHeader(event) {
316
- const workerName = getEventText(event, "workerName");
317
- const intentId = getEventText(event, "intentId");
318
- return Boolean(workerName && intentId && workerName === intentId);
319
- }
320
- function addThinkingHeader(state, header) {
321
- state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header;
322
- }
323
- function addThinkingDetail(state, detail) {
324
- const trimmed = detail.trim();
325
- if (!trimmed) return;
326
- state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + trimmed;
327
- }
328
- function addThinkingLine(state, header, detail) {
329
- state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header + "\n" + detail;
330
- }
331
- function appendThinkingText(state, text) {
332
- state.formattedThinkingText += text;
333
- }
334
391
  function updateExecutionStageMessage(state, msg) {
335
392
  if (!msg) return;
336
393
  for (let i = state.steps.length - 1; i >= 0; i--) {
@@ -351,26 +408,38 @@ function completeLastInProgressStep(steps) {
351
408
  }
352
409
  function createInitialV2State() {
353
410
  return {
354
- formattedThinkingText: "",
355
411
  finalResponse: "",
356
- currentWorker: "",
357
412
  lastEventType: "",
358
413
  sessionId: void 0,
359
414
  executionId: void 0,
360
415
  hasError: false,
361
416
  errorMessage: "",
362
- userActionRequest: void 0,
363
- userActionPending: false,
364
- userActionResult: void 0,
417
+ userActions: [],
418
+ notifications: [],
419
+ lastUserAction: void 0,
420
+ lastNotification: void 0,
365
421
  finalData: void 0,
366
422
  steps: [],
367
423
  stepCounter: 0,
368
424
  currentExecutingStepId: void 0
369
425
  };
370
426
  }
427
+ function upsertUserAction(state, req) {
428
+ const active = { ...req, status: "pending" };
429
+ const matchIdx = state.userActions.findIndex(
430
+ (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
431
+ );
432
+ if (matchIdx >= 0) {
433
+ state.userActions[matchIdx] = active;
434
+ } else {
435
+ state.userActions.push(active);
436
+ }
437
+ }
371
438
  function processStreamEventV2(rawEvent, state) {
372
439
  const event = normalizeEvent(rawEvent);
373
440
  const eventType = event.eventType;
441
+ state.lastUserAction = void 0;
442
+ state.lastNotification = void 0;
374
443
  if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
375
444
  if (event.executionId) state.executionId = event.executionId;
376
445
  if (event.sessionId) state.sessionId = event.sessionId;
@@ -386,10 +455,6 @@ function processStreamEventV2(rawEvent, state) {
386
455
  return state;
387
456
  }
388
457
  if (typeof eventType === "string" && eventType.toUpperCase() === "THINKING_DELTA") {
389
- const text = typeof event.text === "string" ? event.text : "";
390
- if (text) {
391
- appendThinkingText(state, text);
392
- }
393
458
  if (event.executionId) state.executionId = event.executionId;
394
459
  if (event.sessionId) state.sessionId = event.sessionId;
395
460
  state.lastEventType = "THINKING_DELTA";
@@ -400,7 +465,6 @@ function processStreamEventV2(rawEvent, state) {
400
465
  const message = getEventMessage(event);
401
466
  switch (eventType) {
402
467
  case "WORKFLOW_STARTED":
403
- case "STARTED":
404
468
  state.lastEventType = eventType;
405
469
  break;
406
470
  case "INTENT_PROGRESS": {
@@ -413,97 +477,7 @@ function processStreamEventV2(rawEvent, state) {
413
477
  state.lastEventType = eventType;
414
478
  break;
415
479
  }
416
- case "INTENT_THINKING": {
417
- const worker = getEventText(event, "workerName") || "Worker";
418
- const msg = getEventText(event, "message") || "Thinking...";
419
- const showHeader = shouldShowIntentHeader(event);
420
- if (worker !== state.currentWorker) {
421
- state.currentWorker = worker;
422
- if (showHeader && msg && msg !== worker) {
423
- addThinkingLine(state, `**${worker}**`, msg);
424
- } else if (showHeader) {
425
- addThinkingHeader(state, `**${worker}**`);
426
- } else if (msg !== "Thinking...") {
427
- addThinkingDetail(state, msg);
428
- }
429
- } else if ((showHeader || msg !== "Thinking...")) {
430
- appendThinkingText(state, "\n" + msg);
431
- }
432
- const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
433
- if (lastInProgress) {
434
- lastInProgress.thinkingText = "";
435
- lastInProgress.isThinking = true;
436
- }
437
- state.lastEventType = "INTENT_THINKING";
438
- break;
439
- }
440
- case "INTENT_THINKING_CONT": {
441
- const msg = event.message || "";
442
- if (!msg) break;
443
- if (state.lastEventType === "INTENT_THINKING") {
444
- appendThinkingText(state, "\n" + msg);
445
- } else {
446
- appendThinkingText(state, msg);
447
- }
448
- const thinkingStep = [...state.steps].reverse().find((s) => s.isThinking);
449
- if (thinkingStep) {
450
- thinkingStep.thinkingText = (thinkingStep.thinkingText || "") + msg;
451
- }
452
- state.lastEventType = "INTENT_THINKING_CONT";
453
- break;
454
- }
455
- case "ORCHESTRATOR_THINKING": {
456
- addThinkingLine(state, "**Planning**", event.message || "Understanding your request...");
457
- const stepId = `step-${state.stepCounter++}`;
458
- state.steps.push({
459
- id: stepId,
460
- eventType,
461
- message,
462
- status: "in_progress",
463
- timestamp: Date.now(),
464
- elapsedMs: event.elapsedMs
465
- });
466
- state.currentExecutingStepId = stepId;
467
- state.lastEventType = eventType;
468
- break;
469
- }
470
- case "ORCHESTRATOR_COMPLETED": {
471
- const workingDetail = workingPhaseDetailForDisplay(message);
472
- if (workingDetail) {
473
- addThinkingLine(state, "**Working**", workingDetail);
474
- } else {
475
- addThinkingHeader(state, "**Working**");
476
- }
477
- state.steps.push({
478
- id: `step-${state.stepCounter++}`,
479
- eventType: "WORKING",
480
- message: workingDetail,
481
- status: "completed",
482
- timestamp: Date.now()
483
- });
484
- const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
485
- if (step) {
486
- step.status = "completed";
487
- if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
488
- if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
489
- }
490
- state.lastEventType = eventType;
491
- break;
492
- }
493
480
  case "INTENT_STARTED": {
494
- const worker = getEventText(event, "workerName") || "Worker";
495
- const msg = getEventText(event, "message") || "Starting...";
496
- const showHeader = shouldShowIntentHeader(event);
497
- state.currentWorker = worker;
498
- if (showHeader && msg !== worker) {
499
- addThinkingLine(state, `**${worker}**`, msg);
500
- } else if (showHeader) {
501
- addThinkingHeader(state, `**${worker}**`);
502
- } else {
503
- addThinkingDetail(state, msg);
504
- }
505
- const thinkingStep = state.steps.find((s) => s.isThinking);
506
- if (thinkingStep) thinkingStep.isThinking = false;
507
481
  const stepId = `step-${state.stepCounter++}`;
508
482
  state.steps.push({
509
483
  id: stepId,
@@ -522,55 +496,18 @@ function processStreamEventV2(rawEvent, state) {
522
496
  const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
523
497
  if (intentStep) {
524
498
  intentStep.status = "completed";
525
- intentStep.isThinking = false;
526
499
  if (event.elapsedMs) intentStep.elapsedMs = event.elapsedMs;
527
500
  if (intentStep.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
528
501
  }
529
502
  state.lastEventType = eventType;
530
503
  break;
531
504
  }
532
- case "AGGREGATOR_THINKING": {
533
- addThinkingLine(state, "**Finalizing**", event.message || "Preparing response...");
534
- const stepId = `step-${state.stepCounter++}`;
535
- state.steps.push({
536
- id: stepId,
537
- eventType,
538
- message,
539
- status: "in_progress",
540
- timestamp: Date.now(),
541
- elapsedMs: event.elapsedMs
542
- });
543
- state.currentExecutingStepId = stepId;
544
- state.lastEventType = eventType;
545
- break;
546
- }
547
- case "AGGREGATOR_COMPLETED": {
548
- appendThinkingText(state, "\n" + (event.message || "Response ready"));
549
- const step = state.steps.find((s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress");
550
- if (step) {
551
- step.status = "completed";
552
- if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
553
- if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
554
- }
555
- state.lastEventType = eventType;
556
- break;
557
- }
558
- case "WORKFLOW_COMPLETED":
559
- case "COMPLETED": {
505
+ case "WORKFLOW_COMPLETED": {
560
506
  const totalTime = Number(event.totalTimeMs);
561
507
  if (Number.isFinite(totalTime) && totalTime > 0) {
562
508
  state.totalElapsedMs = totalTime;
563
509
  }
564
- let content = extractResponseContent(event.response);
565
- const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
566
- if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
567
- content = trace.workflowMsg;
568
- }
569
- if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
570
- const agg = trace.aggregator;
571
- if (typeof agg.response === "string") content = agg.response;
572
- else content = extractResponseContent(agg.response);
573
- }
510
+ const content = extractResponseContent(event.response);
574
511
  if (content) {
575
512
  state.finalResponse = content;
576
513
  if (event.trace && typeof event.trace === "object") {
@@ -585,178 +522,81 @@ function processStreamEventV2(rawEvent, state) {
585
522
  state.steps.forEach((step) => {
586
523
  if (step.status === "in_progress") {
587
524
  step.status = "completed";
588
- step.isThinking = false;
589
525
  }
590
526
  });
591
527
  state.lastEventType = eventType;
592
528
  break;
593
529
  }
594
530
  case "USER_ACTION_REQUIRED": {
595
- completeLastInProgressStep(state.steps);
596
- if (event.userActionRequest) {
597
- state.userActionRequest = {
598
- userActionId: event.userActionRequest.userActionId,
599
- userActionType: event.userActionRequest.userActionType,
600
- message: event.userActionRequest.message,
601
- requestedSchema: event.userActionRequest.requestedSchema,
602
- metadata: event.userActionRequest.metadata
531
+ const rawAction = typeof event.action === "string" ? event.action : void 0;
532
+ const schema = event.requestedSchema;
533
+ const kind = classifyUserActionKind(rawAction, schema);
534
+ const promptMessage = typeof event.message === "string" && event.message.trim() || message || "";
535
+ const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
536
+ if (kind === "notification") {
537
+ const notification = {
538
+ id: userActionId || `note-${state.stepCounter++}`,
539
+ message: promptMessage
603
540
  };
541
+ state.notifications.push(notification);
542
+ state.lastNotification = notification;
543
+ state.lastEventType = eventType;
544
+ break;
604
545
  }
605
- state.userActionPending = true;
606
- const req = event.userActionRequest;
607
- const displayMessage = getUserActionDisplayMessage(
608
- eventType,
609
- req?.message || message
610
- );
611
- if (req) {
612
- addThinkingLine(state, "**Verification Required**", displayMessage);
546
+ if (!userActionId) {
547
+ state.lastEventType = eventType;
548
+ break;
613
549
  }
614
- const stepId = `step-${state.stepCounter++}`;
615
- state.steps.push({
616
- id: stepId,
617
- eventType,
618
- message: displayMessage,
619
- status: "in_progress",
620
- timestamp: Date.now(),
621
- elapsedMs: event.elapsedMs
622
- });
623
- state.currentExecutingStepId = stepId;
624
- state.lastEventType = eventType;
625
- break;
626
- }
627
- case "USER_ACTION_SUCCESS": {
628
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
629
- appendThinkingText(state, "\n\u2713 " + displayMessage);
630
550
  completeLastInProgressStep(state.steps);
631
- state.userActionRequest = void 0;
632
- state.userActionPending = false;
633
- state.userActionResult = "approved";
551
+ const verificationType = event.verificationType === "ALPHANUMERIC_CODE" || event.verificationType === "NUMERIC_CODE" ? event.verificationType : void 0;
552
+ const request = {
553
+ userActionId,
554
+ kind,
555
+ rawAction,
556
+ subAction: typeof event.subAction === "string" ? event.subAction : void 0,
557
+ verificationType,
558
+ expirySeconds: typeof event.expirySeconds === "number" ? event.expirySeconds : void 0,
559
+ message: promptMessage || void 0,
560
+ requestedSchema: schema,
561
+ metadata: event.metadata,
562
+ toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : void 0,
563
+ executionId: state.executionId,
564
+ sessionId: state.sessionId
565
+ };
566
+ upsertUserAction(state, request);
567
+ state.lastUserAction = request;
634
568
  const stepId = `step-${state.stepCounter++}`;
635
569
  state.steps.push({
636
570
  id: stepId,
637
571
  eventType,
638
- message: displayMessage,
639
- status: "completed",
640
- timestamp: Date.now(),
641
- elapsedMs: event.elapsedMs
642
- });
643
- state.lastEventType = eventType;
644
- break;
645
- }
646
- case "USER_ACTION_INVALID": {
647
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
648
- appendThinkingText(state, "\n\u2717 " + displayMessage);
649
- completeLastInProgressStep(state.steps);
650
- const errorStepId = `step-${state.stepCounter++}`;
651
- state.steps.push({
652
- id: errorStepId,
653
- eventType,
654
- message: displayMessage,
655
- status: "error",
656
- timestamp: Date.now(),
657
- elapsedMs: event.elapsedMs
658
- });
659
- const retryStepId = `step-${state.stepCounter++}`;
660
- state.steps.push({
661
- id: retryStepId,
662
- eventType: "USER_ACTION_REQUIRED",
663
- message: "Waiting for verification...",
572
+ message: promptMessage || (kind === "verification" ? "Waiting for verification..." : "Waiting for your input..."),
664
573
  status: "in_progress",
665
- timestamp: Date.now()
666
- });
667
- state.currentExecutingStepId = retryStepId;
668
- state.lastEventType = eventType;
669
- break;
670
- }
671
- case "USER_ACTION_REJECTED": {
672
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
673
- appendThinkingText(state, "\n" + displayMessage);
674
- completeLastInProgressStep(state.steps);
675
- state.userActionRequest = void 0;
676
- state.userActionPending = false;
677
- state.userActionResult = "rejected";
678
- const stepId = `step-${state.stepCounter++}`;
679
- state.steps.push({
680
- id: stepId,
681
- eventType,
682
- message: displayMessage,
683
- status: "completed",
684
- timestamp: Date.now(),
685
- elapsedMs: event.elapsedMs
686
- });
687
- state.lastEventType = eventType;
688
- break;
689
- }
690
- case "USER_ACTION_EXPIRED": {
691
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
692
- appendThinkingText(state, "\n\u2717 " + displayMessage);
693
- completeLastInProgressStep(state.steps);
694
- state.userActionRequest = void 0;
695
- state.userActionPending = false;
696
- const stepId = `step-${state.stepCounter++}`;
697
- state.steps.push({
698
- id: stepId,
699
- eventType,
700
- message: displayMessage,
701
- status: "error",
702
- timestamp: Date.now(),
703
- elapsedMs: event.elapsedMs
704
- });
705
- state.lastEventType = eventType;
706
- break;
707
- }
708
- case "USER_ACTION_RESENT": {
709
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
710
- appendThinkingText(state, "\n" + displayMessage);
711
- const stepId = `step-${state.stepCounter++}`;
712
- state.steps.push({
713
- id: stepId,
714
- eventType,
715
- message: displayMessage,
716
- status: "completed",
717
574
  timestamp: Date.now(),
718
575
  elapsedMs: event.elapsedMs
719
576
  });
577
+ state.currentExecutingStepId = stepId;
720
578
  state.lastEventType = eventType;
721
579
  break;
722
580
  }
723
- case "USER_ACTION_FAILED": {
724
- const displayMessage = getUserActionDisplayMessage(
725
- eventType,
726
- event.message || event.errorMessage
727
- );
728
- appendThinkingText(state, "\n\u2717 " + displayMessage);
729
- completeLastInProgressStep(state.steps);
730
- state.userActionRequest = void 0;
731
- state.userActionPending = false;
732
- const stepId = `step-${state.stepCounter++}`;
733
- state.steps.push({
734
- id: stepId,
735
- eventType,
736
- message: displayMessage,
737
- status: "error",
738
- timestamp: Date.now(),
739
- elapsedMs: event.elapsedMs
740
- });
581
+ case "USER_NOTIFICATION": {
582
+ const noteMessage = typeof event.message === "string" && event.message.trim() || message || "";
583
+ const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
584
+ if (noteMessage || userActionId) {
585
+ const notification = {
586
+ id: userActionId || `note-${state.stepCounter++}`,
587
+ message: noteMessage
588
+ };
589
+ state.notifications.push(notification);
590
+ state.lastNotification = notification;
591
+ }
741
592
  state.lastEventType = eventType;
742
593
  break;
743
594
  }
744
595
  case "WORKFLOW_ERROR":
745
- case "ERROR":
746
596
  state.hasError = true;
747
597
  state.errorMessage = event.errorMessage || event.message || "Workflow error";
748
598
  state.lastEventType = eventType;
749
599
  break;
750
- case "INTENT_ERROR": {
751
- state.errorMessage = message || event.errorMessage || "An error occurred";
752
- const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
753
- if (intentStep) {
754
- intentStep.status = "error";
755
- intentStep.isThinking = false;
756
- }
757
- state.lastEventType = eventType;
758
- break;
759
- }
760
600
  // ---- K2 pipeline stage lifecycle events ----
761
601
  //
762
602
  // The k2-server playground streaming API emits
@@ -877,96 +717,6 @@ function processStreamEventV2(rawEvent, state) {
877
717
  return state;
878
718
  }
879
719
 
880
- // src/utils/messageStateManager.ts
881
- function buildFormattedThinking(steps, allThinkingText) {
882
- const parts = [];
883
- const safeSteps = steps ?? [];
884
- const cleanAll = allThinkingText.replace(/^\s+/, "");
885
- if (cleanAll) {
886
- const firstStepWithThinking = safeSteps.find(
887
- (s) => s.thinkingText && s.thinkingText.trim()
888
- );
889
- if (!firstStepWithThinking) {
890
- parts.push("**Preflight**");
891
- parts.push(cleanAll);
892
- } else {
893
- const stepText = firstStepWithThinking.thinkingText.trim();
894
- const idx = cleanAll.indexOf(stepText);
895
- if (idx > 0) {
896
- const orphaned = cleanAll.substring(0, idx).replace(/\s+$/, "");
897
- if (orphaned) {
898
- parts.push("**Preflight**");
899
- parts.push(orphaned);
900
- }
901
- }
902
- }
903
- }
904
- for (const step of safeSteps) {
905
- switch (step.eventType) {
906
- case "STAGE_STARTED": {
907
- if (step.message) parts.push(`**${step.message}**`);
908
- break;
909
- }
910
- case "ORCHESTRATOR_THINKING":
911
- parts.push("**Planning**");
912
- if (step.message) parts.push(step.message);
913
- break;
914
- case "INTENT_STARTED": {
915
- let label = step.message || "Processing";
916
- const started = label.match(/^(.+?)\s+started$/i);
917
- const progress = label.match(/^(.+?)\s+in progress$/i);
918
- if (started) label = started[1];
919
- else if (progress) label = progress[1];
920
- parts.push(`**${label}**`);
921
- if (step.thinkingText) parts.push(step.thinkingText);
922
- break;
923
- }
924
- case "INTENT_PROGRESS": {
925
- if (step.thinkingText) parts.push(step.thinkingText);
926
- else if (step.message) parts.push(step.message);
927
- break;
928
- }
929
- case "AGGREGATOR_THINKING":
930
- parts.push("**Finalizing**");
931
- if (step.message) parts.push(step.message);
932
- break;
933
- case "USER_ACTION_REQUIRED":
934
- parts.push("**Verification Required**");
935
- if (step.message) parts.push(step.message);
936
- break;
937
- case "USER_ACTION_SUCCESS":
938
- parts.push(`\u2713 ${step.message || "Verification successful"}`);
939
- break;
940
- case "USER_ACTION_REJECTED":
941
- parts.push(`\u2717 ${step.message || "Verification rejected"}`);
942
- break;
943
- case "USER_ACTION_EXPIRED":
944
- parts.push(`\u2717 ${step.message || "Verification expired"}`);
945
- break;
946
- case "USER_ACTION_FAILED":
947
- parts.push(`\u2717 ${step.message || "Verification failed"}`);
948
- break;
949
- }
950
- }
951
- return parts.length > 0 ? parts.join("\n") : allThinkingText;
952
- }
953
- function createCancelledMessageUpdate(steps, currentMessage) {
954
- const updatedSteps = steps.map((step) => {
955
- if (step.status === "in_progress") {
956
- return { ...step, status: "pending" };
957
- }
958
- return step;
959
- });
960
- return {
961
- isStreaming: false,
962
- isCancelled: true,
963
- steps: updatedSteps,
964
- currentExecutingStepId: void 0,
965
- // Preserve currentMessage so UI can show it with X icon
966
- currentMessage: currentMessage || "Thinking..."
967
- };
968
- }
969
-
970
720
  // src/utils/requestBuilder.ts
971
721
  var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
972
722
  function buildRequestBody(config, userMessage, sessionId, options) {
@@ -1040,115 +790,6 @@ function buildRequestHeaders(config) {
1040
790
  return headers;
1041
791
  }
1042
792
 
1043
- // src/utils/userActionClient.ts
1044
- async function sendUserActionRequest(config, userActionId, action, data) {
1045
- const url = buildUserActionUrl(config, userActionId, action);
1046
- const baseHeaders = buildRequestHeaders(config);
1047
- const hasBody = data !== void 0;
1048
- const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
1049
- const response = await fetch(url, {
1050
- method: "POST",
1051
- headers,
1052
- body: hasBody ? JSON.stringify(data) : void 0
1053
- });
1054
- if (!response.ok) {
1055
- const errorText = await response.text();
1056
- throw new Error(`HTTP ${response.status}: ${errorText}`);
1057
- }
1058
- return await response.json();
1059
- }
1060
- async function submitUserAction(config, userActionId, data) {
1061
- return sendUserActionRequest(config, userActionId, "submit", data);
1062
- }
1063
- async function cancelUserAction(config, userActionId) {
1064
- return sendUserActionRequest(config, userActionId, "cancel");
1065
- }
1066
- async function resendUserAction(config, userActionId) {
1067
- return sendUserActionRequest(config, userActionId, "resend");
1068
- }
1069
- var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
1070
- var chatStore = {
1071
- get(key) {
1072
- const raw = storage.getString(key);
1073
- if (!raw) return [];
1074
- try {
1075
- return JSON.parse(raw);
1076
- } catch {
1077
- return [];
1078
- }
1079
- },
1080
- set(key, messages) {
1081
- storage.set(key, JSON.stringify(messages));
1082
- },
1083
- delete(key) {
1084
- storage.delete(key);
1085
- }
1086
- };
1087
-
1088
- // src/utils/activeStreamStore.ts
1089
- var streams = /* @__PURE__ */ new Map();
1090
- var activeStreamStore = {
1091
- has(key) {
1092
- return streams.has(key);
1093
- },
1094
- get(key) {
1095
- const entry = streams.get(key);
1096
- if (!entry) return null;
1097
- return { messages: entry.messages, isWaiting: entry.isWaiting };
1098
- },
1099
- // Called before startStream — registers the controller and initial messages
1100
- start(key, abortController, initialMessages) {
1101
- const existing = streams.get(key);
1102
- streams.set(key, {
1103
- messages: initialMessages,
1104
- isWaiting: true,
1105
- abortController,
1106
- listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
1107
- });
1108
- },
1109
- // Called by the stream on every event — applies the same updater pattern React uses
1110
- applyMessages(key, updater) {
1111
- const entry = streams.get(key);
1112
- if (!entry) return;
1113
- const next = typeof updater === "function" ? updater(entry.messages) : updater;
1114
- entry.messages = next;
1115
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
1116
- },
1117
- setWaiting(key, waiting) {
1118
- const entry = streams.get(key);
1119
- if (!entry) return;
1120
- entry.isWaiting = waiting;
1121
- entry.listeners.forEach((l) => l(entry.messages, waiting));
1122
- },
1123
- // Called when stream completes — persists to chatStore and cleans up
1124
- complete(key) {
1125
- const entry = streams.get(key);
1126
- if (!entry) return;
1127
- entry.isWaiting = false;
1128
- entry.listeners.forEach((l) => l(entry.messages, false));
1129
- const toSave = entry.messages.filter((m) => !m.isStreaming);
1130
- if (toSave.length > 0) chatStore.set(key, toSave);
1131
- streams.delete(key);
1132
- },
1133
- // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
1134
- subscribe(key, listener) {
1135
- const entry = streams.get(key);
1136
- if (!entry) return () => {
1137
- };
1138
- entry.listeners.add(listener);
1139
- return () => {
1140
- streams.get(key)?.listeners.delete(listener);
1141
- };
1142
- },
1143
- // Explicit user cancel — aborts the controller and removes the entry
1144
- abort(key) {
1145
- const entry = streams.get(key);
1146
- if (!entry) return;
1147
- entry.abortController.abort();
1148
- streams.delete(key);
1149
- }
1150
- };
1151
-
1152
793
  // src/utils/ragImageResolver.ts
1153
794
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
1154
795
  function hasRagImages(content) {
@@ -1236,15 +877,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1236
877
  onEvent: (event) => {
1237
878
  if (abortController.signal.aborted) return;
1238
879
  processStreamEventV2(event, state);
1239
- const eventType = event.eventType;
1240
- if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
1241
- callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
1242
- } else if (typeof eventType === "string" && eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
1243
- const msg = getUserActionDisplayMessage(
1244
- eventType,
1245
- event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
1246
- );
1247
- callbacksRef.current.onUserActionEvent?.(eventType, msg);
880
+ if (state.lastUserAction) {
881
+ callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
882
+ }
883
+ if (state.lastNotification) {
884
+ callbacksRef.current.onUserNotification?.(state.lastNotification);
1248
885
  }
1249
886
  const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
1250
887
  const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
@@ -1267,7 +904,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1267
904
  streamProgress: "error",
1268
905
  isError: true,
1269
906
  errorDetails: state.errorMessage,
1270
- formattedThinkingText: state.formattedThinkingText || void 0,
1271
907
  steps: [...state.steps],
1272
908
  currentExecutingStepId: void 0,
1273
909
  executionId: state.executionId,
@@ -1280,12 +916,10 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1280
916
  currentMessage,
1281
917
  streamProgress: "processing",
1282
918
  isError: false,
1283
- formattedThinkingText: state.formattedThinkingText || void 0,
1284
919
  steps: [...state.steps],
1285
920
  currentExecutingStepId: state.currentExecutingStepId,
1286
921
  executionId: state.executionId,
1287
922
  sessionId: state.sessionId,
1288
- userActionResult: state.userActionResult,
1289
923
  isCancelled: false
1290
924
  });
1291
925
  }
@@ -1296,14 +930,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1296
930
  if (error.name !== "AbortError") {
1297
931
  callbacksRef.current.onError?.(error);
1298
932
  }
1299
- if (state.userActionPending) {
1300
- state.userActionPending = false;
1301
- state.userActionRequest = void 0;
1302
- callbacksRef.current.onUserActionEvent?.(
1303
- "USER_ACTION_FAILED",
1304
- "Connection lost. Please try again."
1305
- );
1306
- }
1307
933
  const isAborted = error.name === "AbortError";
1308
934
  setMessages(
1309
935
  (prev) => prev.map(
@@ -1316,7 +942,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1316
942
  errorDetails: isAborted ? void 0 : error.message,
1317
943
  content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1318
944
  currentMessage: isAborted ? "Thinking..." : void 0,
1319
- formattedThinkingText: state.formattedThinkingText || void 0,
1320
945
  steps: [...state.steps].map((step) => {
1321
946
  if (step.status === "in_progress" && isAborted) {
1322
947
  return { ...step, status: "pending" };
@@ -1332,14 +957,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1332
957
  setIsWaitingForResponse(false);
1333
958
  callbacksRef.current.onStatusMessage?.(null);
1334
959
  callbacksRef.current.onStepsUpdate?.([]);
1335
- if (state.userActionPending) {
1336
- state.userActionPending = false;
1337
- state.userActionRequest = void 0;
1338
- callbacksRef.current.onUserActionEvent?.(
1339
- "USER_ACTION_FAILED",
1340
- "Verification could not be completed."
1341
- );
1342
- }
1343
960
  if (state.sessionId && state.sessionId !== sessionId) {
1344
961
  callbacksRef.current.onSessionIdChange?.(state.sessionId);
1345
962
  }
@@ -1363,8 +980,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1363
980
  steps: state.hasError ? [] : [...state.steps],
1364
981
  isCancelled: false,
1365
982
  currentExecutingStepId: void 0,
1366
- userActionResult: state.userActionResult,
1367
- formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
1368
983
  isResolvingImages: needsImageResolve,
1369
984
  totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
1370
985
  };
@@ -1406,14 +1021,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1406
1021
  if (error.name !== "AbortError") {
1407
1022
  callbacksRef.current.onError?.(error);
1408
1023
  }
1409
- if (state.userActionPending) {
1410
- state.userActionPending = false;
1411
- state.userActionRequest = void 0;
1412
- callbacksRef.current.onUserActionEvent?.(
1413
- "USER_ACTION_FAILED",
1414
- "Connection lost. Please try again."
1415
- );
1416
- }
1417
1024
  const isAborted = error.name === "AbortError";
1418
1025
  setMessages(
1419
1026
  (prev) => prev.map(
@@ -1425,7 +1032,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1425
1032
  isCancelled: isAborted,
1426
1033
  errorDetails: isAborted ? void 0 : error.message,
1427
1034
  content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1428
- formattedThinkingText: state.formattedThinkingText || void 0,
1429
1035
  steps: [...state.steps].map((step) => {
1430
1036
  if (step.status === "in_progress" && isAborted) {
1431
1037
  return { ...step, status: "pending" };
@@ -1451,9 +1057,79 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1451
1057
  };
1452
1058
  }
1453
1059
 
1060
+ // src/utils/messageStateManager.ts
1061
+ function createCancelledMessageUpdate(steps, currentMessage) {
1062
+ const updatedSteps = steps.map((step) => {
1063
+ if (step.status === "in_progress") {
1064
+ return { ...step, status: "pending" };
1065
+ }
1066
+ return step;
1067
+ });
1068
+ return {
1069
+ isStreaming: false,
1070
+ isCancelled: true,
1071
+ steps: updatedSteps,
1072
+ currentExecutingStepId: void 0,
1073
+ currentMessage: currentMessage || "Thinking..."
1074
+ };
1075
+ }
1076
+
1077
+ // src/utils/userActionClient.ts
1078
+ var UserActionStaleError = class extends Error {
1079
+ constructor(userActionId, message = "User action is no longer actionable") {
1080
+ super(message);
1081
+ __publicField(this, "userActionId");
1082
+ this.name = "UserActionStaleError";
1083
+ this.userActionId = userActionId;
1084
+ }
1085
+ };
1086
+ async function sendUserActionRequest(config, userActionId, action, data) {
1087
+ const url = buildUserActionUrl(config, userActionId, action);
1088
+ const baseHeaders = buildRequestHeaders(config);
1089
+ const hasBody = data !== void 0;
1090
+ const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
1091
+ const response = await fetch(url, {
1092
+ method: "POST",
1093
+ headers,
1094
+ body: hasBody ? JSON.stringify(data) : void 0
1095
+ });
1096
+ if (response.status === 404) {
1097
+ throw new UserActionStaleError(userActionId);
1098
+ }
1099
+ if (!response.ok) {
1100
+ const errorText = await response.text();
1101
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1102
+ }
1103
+ return await response.json();
1104
+ }
1105
+ async function submitUserAction(config, userActionId, content) {
1106
+ return sendUserActionRequest(config, userActionId, "submit", content ?? {});
1107
+ }
1108
+ async function cancelUserAction(config, userActionId) {
1109
+ return sendUserActionRequest(config, userActionId, "cancel");
1110
+ }
1111
+ async function resendUserAction(config, userActionId) {
1112
+ return sendUserActionRequest(config, userActionId, "resend");
1113
+ }
1114
+
1454
1115
  // src/hooks/useChatV2.ts
1116
+ var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1117
+ function upsertPrompt(prompts, req) {
1118
+ const active = { ...req, status: "pending" };
1119
+ const idx = prompts.findIndex(
1120
+ (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
1121
+ );
1122
+ if (idx >= 0) {
1123
+ const next = prompts.slice();
1124
+ next[idx] = active;
1125
+ return next;
1126
+ }
1127
+ return [...prompts, active];
1128
+ }
1455
1129
  function getStoredOrInitialMessages(config) {
1456
1130
  if (!config.userId) return config.initialMessages ?? [];
1131
+ const activeStream = activeStreamStore.get(config.userId);
1132
+ if (activeStream) return activeStream.messages;
1457
1133
  const stored = chatStore.get(config.userId);
1458
1134
  if (stored.length > 0) return stored;
1459
1135
  if (config.initialMessages?.length) {
@@ -1467,11 +1143,16 @@ function getSessionIdFromMessages(messages) {
1467
1143
  }
1468
1144
  function useChatV2(config, callbacks = {}) {
1469
1145
  const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
1470
- const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1146
+ const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
1147
+ if (!config.userId) return false;
1148
+ return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1149
+ });
1471
1150
  const sessionIdRef = react.useRef(
1472
1151
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1473
1152
  );
1474
1153
  const prevUserIdRef = react.useRef(config.userId);
1154
+ const streamUserIdRef = react.useRef(void 0);
1155
+ const subscriptionPrevUserIdRef = react.useRef(config.userId);
1475
1156
  const callbacksRef = react.useRef(callbacks);
1476
1157
  callbacksRef.current = callbacks;
1477
1158
  const configRef = react.useRef(config);
@@ -1480,33 +1161,35 @@ function useChatV2(config, callbacks = {}) {
1480
1161
  messagesRef.current = messages;
1481
1162
  const storeAwareSetMessages = react.useCallback(
1482
1163
  (updater) => {
1483
- const { userId } = configRef.current;
1484
- if (userId && activeStreamStore.has(userId)) {
1485
- activeStreamStore.applyMessages(userId, updater);
1164
+ const streamUserId = streamUserIdRef.current;
1165
+ const currentUserId = configRef.current.userId;
1166
+ const storeKey = streamUserId ?? currentUserId;
1167
+ if (storeKey && activeStreamStore.has(storeKey)) {
1168
+ activeStreamStore.applyMessages(storeKey, updater);
1169
+ }
1170
+ if (!streamUserId || streamUserId === currentUserId) {
1171
+ setMessages(updater);
1486
1172
  }
1487
- setMessages(updater);
1488
1173
  },
1489
1174
  // eslint-disable-next-line react-hooks/exhaustive-deps
1490
1175
  []
1491
1176
  );
1492
1177
  const storeAwareSetIsWaiting = react.useCallback(
1493
1178
  (waiting) => {
1494
- const { userId } = configRef.current;
1495
- if (userId && activeStreamStore.has(userId)) {
1496
- activeStreamStore.setWaiting(userId, waiting);
1179
+ const streamUserId = streamUserIdRef.current;
1180
+ const currentUserId = configRef.current.userId;
1181
+ const storeKey = streamUserId ?? currentUserId;
1182
+ if (storeKey && activeStreamStore.has(storeKey)) {
1183
+ activeStreamStore.setWaiting(storeKey, waiting);
1184
+ }
1185
+ if (!streamUserId || streamUserId === currentUserId) {
1186
+ setIsWaitingForResponse(waiting);
1497
1187
  }
1498
- setIsWaitingForResponse(waiting);
1499
1188
  },
1500
1189
  // eslint-disable-next-line react-hooks/exhaustive-deps
1501
1190
  []
1502
1191
  );
1503
- const [userActionState, setUserActionState] = react.useState({
1504
- request: null,
1505
- result: null,
1506
- clearOtpTrigger: 0
1507
- });
1508
- const userActionStateRef = react.useRef(userActionState);
1509
- userActionStateRef.current = userActionState;
1192
+ const [userActionState, setUserActionState] = react.useState(EMPTY_USER_ACTION_STATE);
1510
1193
  const wrappedCallbacks = react.useMemo(() => ({
1511
1194
  ...callbacksRef.current,
1512
1195
  onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
@@ -1516,26 +1199,17 @@ function useChatV2(config, callbacks = {}) {
1516
1199
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1517
1200
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1518
1201
  onUserActionRequired: (request) => {
1519
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1202
+ setUserActionState((prev) => ({
1203
+ ...prev,
1204
+ prompts: upsertPrompt(prev.prompts, request)
1205
+ }));
1520
1206
  callbacksRef.current.onUserActionRequired?.(request);
1521
1207
  },
1522
- onUserActionEvent: (eventType, message) => {
1523
- switch (eventType) {
1524
- case "USER_ACTION_SUCCESS":
1525
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1526
- break;
1527
- case "USER_ACTION_REJECTED":
1528
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1529
- break;
1530
- case "USER_ACTION_EXPIRED":
1531
- case "USER_ACTION_FAILED":
1532
- setUserActionState((prev) => ({ ...prev, request: null }));
1533
- break;
1534
- case "USER_ACTION_INVALID":
1535
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1536
- break;
1537
- }
1538
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1208
+ onUserNotification: (notification) => {
1209
+ setUserActionState(
1210
+ (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1211
+ );
1212
+ callbacksRef.current.onUserNotification?.(notification);
1539
1213
  }
1540
1214
  // eslint-disable-next-line react-hooks/exhaustive-deps
1541
1215
  }), []);
@@ -1583,6 +1257,7 @@ function useChatV2(config, callbacks = {}) {
1583
1257
  const abortController = new AbortController();
1584
1258
  const { userId } = configRef.current;
1585
1259
  if (userId) {
1260
+ streamUserIdRef.current = userId;
1586
1261
  const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1587
1262
  activeStreamStore.start(userId, abortController, initialMessages);
1588
1263
  }
@@ -1593,9 +1268,11 @@ function useChatV2(config, callbacks = {}) {
1593
1268
  abortController,
1594
1269
  options
1595
1270
  );
1596
- if (userId) {
1597
- activeStreamStore.complete(userId);
1271
+ const finalStreamUserId = streamUserIdRef.current ?? userId;
1272
+ if (finalStreamUserId) {
1273
+ activeStreamStore.complete(finalStreamUserId);
1598
1274
  }
1275
+ streamUserIdRef.current = void 0;
1599
1276
  if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1600
1277
  sessionIdRef.current = newSessionId;
1601
1278
  }
@@ -1612,12 +1289,14 @@ function useChatV2(config, callbacks = {}) {
1612
1289
  setMessages((prev) => [...msgs, ...prev]);
1613
1290
  }, []);
1614
1291
  const cancelStream = react.useCallback(() => {
1615
- if (configRef.current.userId) {
1616
- activeStreamStore.abort(configRef.current.userId);
1292
+ const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1293
+ if (streamUserId) {
1294
+ activeStreamStore.abort(streamUserId);
1617
1295
  }
1296
+ streamUserIdRef.current = void 0;
1618
1297
  cancelStreamManager();
1619
1298
  setIsWaitingForResponse(false);
1620
- setUserActionState((prev) => ({ ...prev, request: null, result: null }));
1299
+ setUserActionState((prev) => ({ ...prev, prompts: [] }));
1621
1300
  setMessages(
1622
1301
  (prev) => prev.map((msg) => {
1623
1302
  if (msg.isStreaming) {
@@ -1634,15 +1313,19 @@ function useChatV2(config, callbacks = {}) {
1634
1313
  );
1635
1314
  }, [cancelStreamManager]);
1636
1315
  const resetSession = react.useCallback(() => {
1316
+ const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1317
+ if (streamUserId) {
1318
+ activeStreamStore.abort(streamUserId);
1319
+ }
1637
1320
  if (configRef.current.userId) {
1638
- activeStreamStore.abort(configRef.current.userId);
1639
1321
  chatStore.delete(configRef.current.userId);
1640
1322
  }
1323
+ streamUserIdRef.current = void 0;
1641
1324
  setMessages([]);
1642
1325
  sessionIdRef.current = void 0;
1643
1326
  abortControllerRef.current?.abort();
1644
1327
  setIsWaitingForResponse(false);
1645
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1328
+ setUserActionState(EMPTY_USER_ACTION_STATE);
1646
1329
  }, []);
1647
1330
  const getSessionId = react.useCallback(() => {
1648
1331
  return sessionIdRef.current;
@@ -1650,59 +1333,91 @@ function useChatV2(config, callbacks = {}) {
1650
1333
  const getMessages = react.useCallback(() => {
1651
1334
  return messages;
1652
1335
  }, [messages]);
1653
- const approveUserAction = react.useCallback(
1654
- async (otp) => {
1655
- const request = userActionStateRef.current.request;
1656
- if (!request) return;
1336
+ const setPromptStatus = react.useCallback(
1337
+ (userActionId, status) => {
1338
+ setUserActionState((prev) => ({
1339
+ ...prev,
1340
+ prompts: prev.prompts.map(
1341
+ (p) => p.userActionId === userActionId ? { ...p, status } : p
1342
+ )
1343
+ }));
1344
+ },
1345
+ []
1346
+ );
1347
+ const removePrompt = react.useCallback((userActionId) => {
1348
+ setUserActionState((prev) => ({
1349
+ ...prev,
1350
+ prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
1351
+ }));
1352
+ }, []);
1353
+ const submitUserAction2 = react.useCallback(
1354
+ async (userActionId, content) => {
1355
+ setPromptStatus(userActionId, "submitting");
1657
1356
  try {
1658
- await submitUserAction(configRef.current, request.userActionId, { otp });
1357
+ await submitUserAction(configRef.current, userActionId, content);
1358
+ removePrompt(userActionId);
1659
1359
  } catch (error) {
1660
- setUserActionState((prev) => ({
1661
- ...prev,
1662
- clearOtpTrigger: prev.clearOtpTrigger + 1
1663
- }));
1360
+ if (error instanceof UserActionStaleError) {
1361
+ setPromptStatus(userActionId, "stale");
1362
+ return;
1363
+ }
1364
+ setPromptStatus(userActionId, "pending");
1664
1365
  callbacksRef.current.onError?.(error);
1665
1366
  throw error;
1666
1367
  }
1667
1368
  },
1668
- []
1369
+ [removePrompt, setPromptStatus]
1669
1370
  );
1670
- const rejectUserAction = react.useCallback(async () => {
1671
- const request = userActionStateRef.current.request;
1672
- if (!request) return;
1673
- try {
1674
- setMessages((prev) => {
1675
- let lastStreamingIdx = -1;
1676
- for (let i = prev.length - 1; i >= 0; i--) {
1677
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
1678
- lastStreamingIdx = i;
1679
- break;
1680
- }
1371
+ const cancelUserAction2 = react.useCallback(
1372
+ async (userActionId) => {
1373
+ setPromptStatus(userActionId, "submitting");
1374
+ try {
1375
+ await cancelUserAction(configRef.current, userActionId);
1376
+ removePrompt(userActionId);
1377
+ } catch (error) {
1378
+ if (error instanceof UserActionStaleError) {
1379
+ removePrompt(userActionId);
1380
+ return;
1681
1381
  }
1682
- if (lastStreamingIdx === -1) return prev;
1683
- return prev.map(
1684
- (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
1685
- );
1686
- });
1687
- await cancelUserAction(configRef.current, request.userActionId);
1688
- } catch (error) {
1689
- callbacksRef.current.onError?.(error);
1690
- throw error;
1691
- }
1692
- }, []);
1693
- const resendOtp = react.useCallback(async () => {
1694
- const request = userActionStateRef.current.request;
1695
- if (!request) return;
1696
- try {
1697
- await resendUserAction(configRef.current, request.userActionId);
1698
- } catch (error) {
1699
- callbacksRef.current.onError?.(error);
1700
- throw error;
1701
- }
1382
+ setPromptStatus(userActionId, "pending");
1383
+ callbacksRef.current.onError?.(error);
1384
+ throw error;
1385
+ }
1386
+ },
1387
+ [removePrompt, setPromptStatus]
1388
+ );
1389
+ const resendUserAction2 = react.useCallback(
1390
+ async (userActionId) => {
1391
+ setPromptStatus(userActionId, "submitting");
1392
+ try {
1393
+ await resendUserAction(configRef.current, userActionId);
1394
+ setPromptStatus(userActionId, "pending");
1395
+ } catch (error) {
1396
+ if (error instanceof UserActionStaleError) {
1397
+ setPromptStatus(userActionId, "stale");
1398
+ return;
1399
+ }
1400
+ setPromptStatus(userActionId, "pending");
1401
+ callbacksRef.current.onError?.(error);
1402
+ throw error;
1403
+ }
1404
+ },
1405
+ [setPromptStatus]
1406
+ );
1407
+ const dismissNotification = react.useCallback((id) => {
1408
+ setUserActionState((prev) => ({
1409
+ ...prev,
1410
+ notifications: prev.notifications.filter((n) => n.id !== id)
1411
+ }));
1702
1412
  }, []);
1703
1413
  react.useEffect(() => {
1414
+ const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
1415
+ subscriptionPrevUserIdRef.current = config.userId;
1704
1416
  const { userId } = config;
1705
1417
  if (!userId) return;
1418
+ if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
1419
+ streamUserIdRef.current = userId;
1420
+ }
1706
1421
  const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1707
1422
  setMessages(msgs);
1708
1423
  setIsWaitingForResponse(isWaiting);
@@ -1716,6 +1431,7 @@ function useChatV2(config, callbacks = {}) {
1716
1431
  }, [config.userId]);
1717
1432
  react.useEffect(() => {
1718
1433
  if (!config.userId) return;
1434
+ if (prevUserIdRef.current !== config.userId) return;
1719
1435
  const toSave = messages.filter((m) => !m.isStreaming);
1720
1436
  if (toSave.length > 0) {
1721
1437
  chatStore.set(config.userId, toSave);
@@ -1737,7 +1453,7 @@ function useChatV2(config, callbacks = {}) {
1737
1453
  setMessages([]);
1738
1454
  sessionIdRef.current = void 0;
1739
1455
  setIsWaitingForResponse(false);
1740
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1456
+ setUserActionState(EMPTY_USER_ACTION_STATE);
1741
1457
  } else if (config.userId) {
1742
1458
  const active = activeStreamStore.get(config.userId);
1743
1459
  if (active) {
@@ -1764,9 +1480,10 @@ function useChatV2(config, callbacks = {}) {
1764
1480
  isWaitingForResponse,
1765
1481
  sessionId: sessionIdRef.current,
1766
1482
  userActionState,
1767
- approveUserAction,
1768
- rejectUserAction,
1769
- resendOtp
1483
+ submitUserAction: submitUserAction2,
1484
+ cancelUserAction: cancelUserAction2,
1485
+ resendUserAction: resendUserAction2,
1486
+ dismissNotification
1770
1487
  };
1771
1488
  }
1772
1489
  function useVoice() {
@@ -1885,15 +1602,150 @@ function useVoice() {
1885
1602
  };
1886
1603
  }
1887
1604
 
1888
- exports.buildFormattedThinking = buildFormattedThinking;
1605
+ // src/utils/jsonSchemaForm.ts
1606
+ function classifyField(field) {
1607
+ if (!field) return "text";
1608
+ if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
1609
+ switch (field.type) {
1610
+ case "boolean":
1611
+ return "boolean";
1612
+ case "integer":
1613
+ return "integer";
1614
+ case "number":
1615
+ return "decimal";
1616
+ case "string":
1617
+ return "text";
1618
+ default:
1619
+ return "text";
1620
+ }
1621
+ }
1622
+ function isNestedOrUnsupported(field) {
1623
+ if (!field) return false;
1624
+ if (field.type === "object" || field.type === "array") return true;
1625
+ if ("properties" in field && field.properties != null) return true;
1626
+ if ("items" in field && field.items != null) return true;
1627
+ return false;
1628
+ }
1629
+ function getOptions(field) {
1630
+ if (!field || !Array.isArray(field.oneOf)) return [];
1631
+ return field.oneOf.filter(
1632
+ (o) => !!o && typeof o === "object" && typeof o.const === "string"
1633
+ );
1634
+ }
1635
+ function isRequired(schema, key) {
1636
+ return Array.isArray(schema?.required) && schema.required.includes(key);
1637
+ }
1638
+ function coerceValue(field, raw) {
1639
+ const widget = classifyField(field);
1640
+ if (widget === "boolean") {
1641
+ if (typeof raw === "boolean") return raw;
1642
+ if (raw === "true") return true;
1643
+ if (raw === "false") return false;
1644
+ return Boolean(raw);
1645
+ }
1646
+ if (widget === "integer" || widget === "decimal") {
1647
+ if (raw === "" || raw == null) return void 0;
1648
+ const num = typeof raw === "number" ? raw : Number(String(raw).trim());
1649
+ if (Number.isNaN(num)) return void 0;
1650
+ return widget === "integer" ? Math.trunc(num) : num;
1651
+ }
1652
+ if (raw == null) return void 0;
1653
+ const str = String(raw);
1654
+ return str === "" ? void 0 : str;
1655
+ }
1656
+ function defaultValueFor(field) {
1657
+ if (!field || field.default === void 0) {
1658
+ return classifyField(field) === "boolean" ? false : "";
1659
+ }
1660
+ return field.default;
1661
+ }
1662
+ function validateField(field, value, required) {
1663
+ const widget = classifyField(field);
1664
+ const label = field?.title || "This field";
1665
+ const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
1666
+ if (isEmpty) {
1667
+ if (required && widget !== "boolean") return `${label} is required.`;
1668
+ return null;
1669
+ }
1670
+ if (widget === "integer" || widget === "decimal") {
1671
+ const num = typeof value === "number" ? value : Number(value);
1672
+ if (Number.isNaN(num)) return `${label} must be a number.`;
1673
+ if (widget === "integer" && !Number.isInteger(num)) {
1674
+ return `${label} must be a whole number.`;
1675
+ }
1676
+ if (typeof field?.minimum === "number" && num < field.minimum) {
1677
+ return `${label} must be at least ${field.minimum}.`;
1678
+ }
1679
+ if (typeof field?.maximum === "number" && num > field.maximum) {
1680
+ return `${label} must be at most ${field.maximum}.`;
1681
+ }
1682
+ return null;
1683
+ }
1684
+ if (widget === "select") {
1685
+ const allowed = getOptions(field).map((o) => o.const);
1686
+ if (allowed.length > 0 && !allowed.includes(String(value))) {
1687
+ return `${label} has an invalid selection.`;
1688
+ }
1689
+ return null;
1690
+ }
1691
+ const str = String(value);
1692
+ if (typeof field?.minLength === "number" && str.length < field.minLength) {
1693
+ return `${label} must be at least ${field.minLength} characters.`;
1694
+ }
1695
+ if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
1696
+ return `${label} must be at most ${field.maxLength} characters.`;
1697
+ }
1698
+ return null;
1699
+ }
1700
+ function renderableFields(schema) {
1701
+ const props = schema?.properties;
1702
+ if (!props) return [];
1703
+ return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
1704
+ }
1705
+ function validateForm(schema, values) {
1706
+ const errors = {};
1707
+ for (const [key, field] of renderableFields(schema)) {
1708
+ const coerced = coerceValue(field, values[key]);
1709
+ const err = validateField(field, coerced, isRequired(schema, key));
1710
+ if (err) errors[key] = err;
1711
+ }
1712
+ return errors;
1713
+ }
1714
+ function buildContent(schema, values) {
1715
+ const content = {};
1716
+ for (const [key, field] of renderableFields(schema)) {
1717
+ const coerced = coerceValue(field, values[key]);
1718
+ if (coerced !== void 0) content[key] = coerced;
1719
+ }
1720
+ return content;
1721
+ }
1722
+
1723
+ // src/index.ts
1724
+ function migrateActiveStream(oldUserId, newUserId) {
1725
+ activeStreamStore.rename(oldUserId, newUserId);
1726
+ }
1727
+
1728
+ exports.UserActionStaleError = UserActionStaleError;
1729
+ exports.buildContent = buildContent;
1889
1730
  exports.cancelUserAction = cancelUserAction;
1731
+ exports.classifyField = classifyField;
1732
+ exports.classifyUserActionKind = classifyUserActionKind;
1733
+ exports.coerceValue = coerceValue;
1890
1734
  exports.createInitialV2State = createInitialV2State;
1735
+ exports.defaultValueFor = defaultValueFor;
1891
1736
  exports.generateId = generateId;
1737
+ exports.getOptions = getOptions;
1738
+ exports.isNestedOrUnsupported = isNestedOrUnsupported;
1739
+ exports.isRequired = isRequired;
1740
+ exports.migrateActiveStream = migrateActiveStream;
1892
1741
  exports.processStreamEventV2 = processStreamEventV2;
1742
+ exports.renderableFields = renderableFields;
1893
1743
  exports.resendUserAction = resendUserAction;
1894
1744
  exports.streamWorkflowEvents = streamWorkflowEvents;
1895
1745
  exports.submitUserAction = submitUserAction;
1896
1746
  exports.useChatV2 = useChatV2;
1897
1747
  exports.useVoice = useVoice;
1748
+ exports.validateField = validateField;
1749
+ exports.validateForm = validateForm;
1898
1750
  //# sourceMappingURL=index.native.js.map
1899
1751
  //# sourceMappingURL=index.native.js.map