@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.
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  var react = require('react');
4
4
 
5
- // src/hooks/useChatV2.ts
5
+ var __defProp = Object.defineProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
6
8
 
7
9
  // src/utils/generateId.ts
8
10
  function generateId() {
@@ -13,10 +15,95 @@ function generateId() {
13
15
  });
14
16
  }
15
17
 
18
+ // src/utils/chatStore.ts
19
+ var memoryStore = /* @__PURE__ */ new Map();
20
+ var chatStore = {
21
+ get(key) {
22
+ return memoryStore.get(key) ?? [];
23
+ },
24
+ set(key, messages) {
25
+ memoryStore.set(key, messages);
26
+ },
27
+ delete(key) {
28
+ memoryStore.delete(key);
29
+ }
30
+ };
31
+
32
+ // src/utils/activeStreamStore.ts
33
+ var streams = /* @__PURE__ */ new Map();
34
+ var activeStreamStore = {
35
+ has(key) {
36
+ return streams.has(key);
37
+ },
38
+ get(key) {
39
+ const entry = streams.get(key);
40
+ if (!entry) return null;
41
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
42
+ },
43
+ // Called before startStream — registers the controller and initial messages
44
+ start(key, abortController, initialMessages) {
45
+ const existing = streams.get(key);
46
+ streams.set(key, {
47
+ messages: initialMessages,
48
+ isWaiting: true,
49
+ abortController,
50
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
51
+ });
52
+ },
53
+ // Called by the stream on every event — applies the same updater pattern React uses
54
+ applyMessages(key, updater) {
55
+ const entry = streams.get(key);
56
+ if (!entry) return;
57
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
58
+ entry.messages = next;
59
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
60
+ },
61
+ setWaiting(key, waiting) {
62
+ const entry = streams.get(key);
63
+ if (!entry) return;
64
+ entry.isWaiting = waiting;
65
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
66
+ },
67
+ // Called when stream completes — persists to chatStore and cleans up
68
+ complete(key) {
69
+ const entry = streams.get(key);
70
+ if (!entry) return;
71
+ entry.isWaiting = false;
72
+ entry.listeners.forEach((l) => l(entry.messages, false));
73
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
74
+ if (toSave.length > 0) chatStore.set(key, toSave);
75
+ streams.delete(key);
76
+ },
77
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
78
+ subscribe(key, listener) {
79
+ const entry = streams.get(key);
80
+ if (!entry) return () => {
81
+ };
82
+ entry.listeners.add(listener);
83
+ return () => {
84
+ streams.get(key)?.listeners.delete(listener);
85
+ };
86
+ },
87
+ // Rename an entry — used when the server assigns a new session ID mid-stream
88
+ rename(oldKey, newKey) {
89
+ const entry = streams.get(oldKey);
90
+ if (!entry) return;
91
+ streams.set(newKey, entry);
92
+ streams.delete(oldKey);
93
+ },
94
+ // Explicit user cancel — aborts the controller and removes the entry
95
+ abort(key) {
96
+ const entry = streams.get(key);
97
+ if (!entry) return;
98
+ entry.abortController.abort();
99
+ streams.delete(key);
100
+ }
101
+ };
102
+
16
103
  // src/utils/streamingClient.ts
17
104
  function yieldAfterProgressEvent(event) {
18
105
  const t = event.eventType;
19
- if (t === "RUN_IN_PROGRESS" || t === "INTENT_PROGRESS" || t === "AGGREGATOR_THINKING_CONT" || t === "INTENT_THINKING_CONT" || t === "THINKING_DELTA") {
106
+ if (t === "RUN_IN_PROGRESS" || t === "INTENT_PROGRESS" || t === "THINKING_DELTA") {
20
107
  return new Promise((resolve) => setTimeout(resolve, 0));
21
108
  }
22
109
  return Promise.resolve();
@@ -258,62 +345,28 @@ function normalizeEvent(event) {
258
345
  return event;
259
346
  }
260
347
  }
261
- function isUserActionPrompt(text) {
262
- 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);
348
+ function isVerificationSchema(schema) {
349
+ const props = schema?.properties;
350
+ if (!props) return false;
351
+ const keys = Object.keys(props);
352
+ return keys.length === 1 && keys[0] === "verificationCode";
263
353
  }
264
- function getUserActionDisplayMessage(eventType, rawMessage) {
265
- const raw = rawMessage?.trim();
266
- const safeRaw = raw && !isUserActionPrompt(raw) ? raw : "";
267
- switch (eventType) {
268
- case "USER_ACTION_REQUIRED":
269
- return raw || "Waiting for verification...";
270
- case "USER_ACTION_SUCCESS":
271
- return safeRaw || "Verification approved";
272
- case "USER_ACTION_INVALID":
273
- return safeRaw || "Invalid code. Please try again.";
274
- case "USER_ACTION_REJECTED":
275
- return safeRaw || "Verification cancelled";
276
- case "USER_ACTION_EXPIRED":
277
- return safeRaw || "Verification expired";
278
- case "USER_ACTION_RESENT":
279
- return safeRaw || "Verification code resent";
280
- case "USER_ACTION_FAILED":
281
- return safeRaw || "Verification failed";
354
+ function classifyUserActionKind(action, schema) {
355
+ switch ((action || "").toLowerCase()) {
356
+ case "userverificationrequest":
357
+ return "verification";
358
+ case "usernotificationrequest":
359
+ return "notification";
360
+ case "userformrequest":
361
+ return isVerificationSchema(schema) ? "verification" : "form";
282
362
  default:
283
- return safeRaw || raw || eventType;
284
- }
285
- }
286
- function workingPhaseDetailForDisplay(raw) {
287
- const t = raw.trim();
288
- if (!t) return "";
289
- if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
290
- return "";
363
+ return isVerificationSchema(schema) ? "verification" : "form";
291
364
  }
292
- return t;
293
365
  }
294
366
  function getEventText(event, field) {
295
367
  const value = event[field];
296
368
  return typeof value === "string" ? value.trim() : "";
297
369
  }
298
- function shouldShowIntentHeader(event) {
299
- const workerName = getEventText(event, "workerName");
300
- const intentId = getEventText(event, "intentId");
301
- return Boolean(workerName && intentId && workerName === intentId);
302
- }
303
- function addThinkingHeader(state, header) {
304
- state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header;
305
- }
306
- function addThinkingDetail(state, detail) {
307
- const trimmed = detail.trim();
308
- if (!trimmed) return;
309
- state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + trimmed;
310
- }
311
- function addThinkingLine(state, header, detail) {
312
- state.formattedThinkingText += (state.formattedThinkingText ? "\n" : "") + header + "\n" + detail;
313
- }
314
- function appendThinkingText(state, text) {
315
- state.formattedThinkingText += text;
316
- }
317
370
  function updateExecutionStageMessage(state, msg) {
318
371
  if (!msg) return;
319
372
  for (let i = state.steps.length - 1; i >= 0; i--) {
@@ -334,26 +387,38 @@ function completeLastInProgressStep(steps) {
334
387
  }
335
388
  function createInitialV2State() {
336
389
  return {
337
- formattedThinkingText: "",
338
390
  finalResponse: "",
339
- currentWorker: "",
340
391
  lastEventType: "",
341
392
  sessionId: void 0,
342
393
  executionId: void 0,
343
394
  hasError: false,
344
395
  errorMessage: "",
345
- userActionRequest: void 0,
346
- userActionPending: false,
347
- userActionResult: void 0,
396
+ userActions: [],
397
+ notifications: [],
398
+ lastUserAction: void 0,
399
+ lastNotification: void 0,
348
400
  finalData: void 0,
349
401
  steps: [],
350
402
  stepCounter: 0,
351
403
  currentExecutingStepId: void 0
352
404
  };
353
405
  }
406
+ function upsertUserAction(state, req) {
407
+ const active = { ...req, status: "pending" };
408
+ const matchIdx = state.userActions.findIndex(
409
+ (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
410
+ );
411
+ if (matchIdx >= 0) {
412
+ state.userActions[matchIdx] = active;
413
+ } else {
414
+ state.userActions.push(active);
415
+ }
416
+ }
354
417
  function processStreamEventV2(rawEvent, state) {
355
418
  const event = normalizeEvent(rawEvent);
356
419
  const eventType = event.eventType;
420
+ state.lastUserAction = void 0;
421
+ state.lastNotification = void 0;
357
422
  if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
358
423
  if (event.executionId) state.executionId = event.executionId;
359
424
  if (event.sessionId) state.sessionId = event.sessionId;
@@ -369,10 +434,6 @@ function processStreamEventV2(rawEvent, state) {
369
434
  return state;
370
435
  }
371
436
  if (typeof eventType === "string" && eventType.toUpperCase() === "THINKING_DELTA") {
372
- const text = typeof event.text === "string" ? event.text : "";
373
- if (text) {
374
- appendThinkingText(state, text);
375
- }
376
437
  if (event.executionId) state.executionId = event.executionId;
377
438
  if (event.sessionId) state.sessionId = event.sessionId;
378
439
  state.lastEventType = "THINKING_DELTA";
@@ -383,7 +444,6 @@ function processStreamEventV2(rawEvent, state) {
383
444
  const message = getEventMessage(event);
384
445
  switch (eventType) {
385
446
  case "WORKFLOW_STARTED":
386
- case "STARTED":
387
447
  state.lastEventType = eventType;
388
448
  break;
389
449
  case "INTENT_PROGRESS": {
@@ -396,97 +456,7 @@ function processStreamEventV2(rawEvent, state) {
396
456
  state.lastEventType = eventType;
397
457
  break;
398
458
  }
399
- case "INTENT_THINKING": {
400
- const worker = getEventText(event, "workerName") || "Worker";
401
- const msg = getEventText(event, "message") || "Thinking...";
402
- const showHeader = shouldShowIntentHeader(event);
403
- if (worker !== state.currentWorker) {
404
- state.currentWorker = worker;
405
- if (showHeader && msg && msg !== worker) {
406
- addThinkingLine(state, `**${worker}**`, msg);
407
- } else if (showHeader) {
408
- addThinkingHeader(state, `**${worker}**`);
409
- } else if (msg !== "Thinking...") {
410
- addThinkingDetail(state, msg);
411
- }
412
- } else if ((showHeader || msg !== "Thinking...")) {
413
- appendThinkingText(state, "\n" + msg);
414
- }
415
- const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
416
- if (lastInProgress) {
417
- lastInProgress.thinkingText = "";
418
- lastInProgress.isThinking = true;
419
- }
420
- state.lastEventType = "INTENT_THINKING";
421
- break;
422
- }
423
- case "INTENT_THINKING_CONT": {
424
- const msg = event.message || "";
425
- if (!msg) break;
426
- if (state.lastEventType === "INTENT_THINKING") {
427
- appendThinkingText(state, "\n" + msg);
428
- } else {
429
- appendThinkingText(state, msg);
430
- }
431
- const thinkingStep = [...state.steps].reverse().find((s) => s.isThinking);
432
- if (thinkingStep) {
433
- thinkingStep.thinkingText = (thinkingStep.thinkingText || "") + msg;
434
- }
435
- state.lastEventType = "INTENT_THINKING_CONT";
436
- break;
437
- }
438
- case "ORCHESTRATOR_THINKING": {
439
- addThinkingLine(state, "**Planning**", event.message || "Understanding your request...");
440
- const stepId = `step-${state.stepCounter++}`;
441
- state.steps.push({
442
- id: stepId,
443
- eventType,
444
- message,
445
- status: "in_progress",
446
- timestamp: Date.now(),
447
- elapsedMs: event.elapsedMs
448
- });
449
- state.currentExecutingStepId = stepId;
450
- state.lastEventType = eventType;
451
- break;
452
- }
453
- case "ORCHESTRATOR_COMPLETED": {
454
- const workingDetail = workingPhaseDetailForDisplay(message);
455
- if (workingDetail) {
456
- addThinkingLine(state, "**Working**", workingDetail);
457
- } else {
458
- addThinkingHeader(state, "**Working**");
459
- }
460
- state.steps.push({
461
- id: `step-${state.stepCounter++}`,
462
- eventType: "WORKING",
463
- message: workingDetail,
464
- status: "completed",
465
- timestamp: Date.now()
466
- });
467
- const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
468
- if (step) {
469
- step.status = "completed";
470
- if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
471
- if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
472
- }
473
- state.lastEventType = eventType;
474
- break;
475
- }
476
459
  case "INTENT_STARTED": {
477
- const worker = getEventText(event, "workerName") || "Worker";
478
- const msg = getEventText(event, "message") || "Starting...";
479
- const showHeader = shouldShowIntentHeader(event);
480
- state.currentWorker = worker;
481
- if (showHeader && msg !== worker) {
482
- addThinkingLine(state, `**${worker}**`, msg);
483
- } else if (showHeader) {
484
- addThinkingHeader(state, `**${worker}**`);
485
- } else {
486
- addThinkingDetail(state, msg);
487
- }
488
- const thinkingStep = state.steps.find((s) => s.isThinking);
489
- if (thinkingStep) thinkingStep.isThinking = false;
490
460
  const stepId = `step-${state.stepCounter++}`;
491
461
  state.steps.push({
492
462
  id: stepId,
@@ -505,55 +475,18 @@ function processStreamEventV2(rawEvent, state) {
505
475
  const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
506
476
  if (intentStep) {
507
477
  intentStep.status = "completed";
508
- intentStep.isThinking = false;
509
478
  if (event.elapsedMs) intentStep.elapsedMs = event.elapsedMs;
510
479
  if (intentStep.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
511
480
  }
512
481
  state.lastEventType = eventType;
513
482
  break;
514
483
  }
515
- case "AGGREGATOR_THINKING": {
516
- addThinkingLine(state, "**Finalizing**", event.message || "Preparing response...");
517
- const stepId = `step-${state.stepCounter++}`;
518
- state.steps.push({
519
- id: stepId,
520
- eventType,
521
- message,
522
- status: "in_progress",
523
- timestamp: Date.now(),
524
- elapsedMs: event.elapsedMs
525
- });
526
- state.currentExecutingStepId = stepId;
527
- state.lastEventType = eventType;
528
- break;
529
- }
530
- case "AGGREGATOR_COMPLETED": {
531
- appendThinkingText(state, "\n" + (event.message || "Response ready"));
532
- const step = state.steps.find((s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress");
533
- if (step) {
534
- step.status = "completed";
535
- if (event.elapsedMs) step.elapsedMs = event.elapsedMs;
536
- if (step.id === state.currentExecutingStepId) state.currentExecutingStepId = void 0;
537
- }
538
- state.lastEventType = eventType;
539
- break;
540
- }
541
- case "WORKFLOW_COMPLETED":
542
- case "COMPLETED": {
484
+ case "WORKFLOW_COMPLETED": {
543
485
  const totalTime = Number(event.totalTimeMs);
544
486
  if (Number.isFinite(totalTime) && totalTime > 0) {
545
487
  state.totalElapsedMs = totalTime;
546
488
  }
547
- let content = extractResponseContent(event.response);
548
- const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
549
- if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
550
- content = trace.workflowMsg;
551
- }
552
- if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
553
- const agg = trace.aggregator;
554
- if (typeof agg.response === "string") content = agg.response;
555
- else content = extractResponseContent(agg.response);
556
- }
489
+ const content = extractResponseContent(event.response);
557
490
  if (content) {
558
491
  state.finalResponse = content;
559
492
  if (event.trace && typeof event.trace === "object") {
@@ -568,178 +501,81 @@ function processStreamEventV2(rawEvent, state) {
568
501
  state.steps.forEach((step) => {
569
502
  if (step.status === "in_progress") {
570
503
  step.status = "completed";
571
- step.isThinking = false;
572
504
  }
573
505
  });
574
506
  state.lastEventType = eventType;
575
507
  break;
576
508
  }
577
509
  case "USER_ACTION_REQUIRED": {
578
- completeLastInProgressStep(state.steps);
579
- if (event.userActionRequest) {
580
- state.userActionRequest = {
581
- userActionId: event.userActionRequest.userActionId,
582
- userActionType: event.userActionRequest.userActionType,
583
- message: event.userActionRequest.message,
584
- requestedSchema: event.userActionRequest.requestedSchema,
585
- metadata: event.userActionRequest.metadata
510
+ const rawAction = typeof event.action === "string" ? event.action : void 0;
511
+ const schema = event.requestedSchema;
512
+ const kind = classifyUserActionKind(rawAction, schema);
513
+ const promptMessage = typeof event.message === "string" && event.message.trim() || message || "";
514
+ const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
515
+ if (kind === "notification") {
516
+ const notification = {
517
+ id: userActionId || `note-${state.stepCounter++}`,
518
+ message: promptMessage
586
519
  };
520
+ state.notifications.push(notification);
521
+ state.lastNotification = notification;
522
+ state.lastEventType = eventType;
523
+ break;
587
524
  }
588
- state.userActionPending = true;
589
- const req = event.userActionRequest;
590
- const displayMessage = getUserActionDisplayMessage(
591
- eventType,
592
- req?.message || message
593
- );
594
- if (req) {
595
- addThinkingLine(state, "**Verification Required**", displayMessage);
525
+ if (!userActionId) {
526
+ state.lastEventType = eventType;
527
+ break;
596
528
  }
597
- const stepId = `step-${state.stepCounter++}`;
598
- state.steps.push({
599
- id: stepId,
600
- eventType,
601
- message: displayMessage,
602
- status: "in_progress",
603
- timestamp: Date.now(),
604
- elapsedMs: event.elapsedMs
605
- });
606
- state.currentExecutingStepId = stepId;
607
- state.lastEventType = eventType;
608
- break;
609
- }
610
- case "USER_ACTION_SUCCESS": {
611
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
612
- appendThinkingText(state, "\n\u2713 " + displayMessage);
613
529
  completeLastInProgressStep(state.steps);
614
- state.userActionRequest = void 0;
615
- state.userActionPending = false;
616
- state.userActionResult = "approved";
530
+ const verificationType = event.verificationType === "ALPHANUMERIC_CODE" || event.verificationType === "NUMERIC_CODE" ? event.verificationType : void 0;
531
+ const request = {
532
+ userActionId,
533
+ kind,
534
+ rawAction,
535
+ subAction: typeof event.subAction === "string" ? event.subAction : void 0,
536
+ verificationType,
537
+ expirySeconds: typeof event.expirySeconds === "number" ? event.expirySeconds : void 0,
538
+ message: promptMessage || void 0,
539
+ requestedSchema: schema,
540
+ metadata: event.metadata,
541
+ toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : void 0,
542
+ executionId: state.executionId,
543
+ sessionId: state.sessionId
544
+ };
545
+ upsertUserAction(state, request);
546
+ state.lastUserAction = request;
617
547
  const stepId = `step-${state.stepCounter++}`;
618
548
  state.steps.push({
619
549
  id: stepId,
620
550
  eventType,
621
- message: displayMessage,
622
- status: "completed",
623
- timestamp: Date.now(),
624
- elapsedMs: event.elapsedMs
625
- });
626
- state.lastEventType = eventType;
627
- break;
628
- }
629
- case "USER_ACTION_INVALID": {
630
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
631
- appendThinkingText(state, "\n\u2717 " + displayMessage);
632
- completeLastInProgressStep(state.steps);
633
- const errorStepId = `step-${state.stepCounter++}`;
634
- state.steps.push({
635
- id: errorStepId,
636
- eventType,
637
- message: displayMessage,
638
- status: "error",
639
- timestamp: Date.now(),
640
- elapsedMs: event.elapsedMs
641
- });
642
- const retryStepId = `step-${state.stepCounter++}`;
643
- state.steps.push({
644
- id: retryStepId,
645
- eventType: "USER_ACTION_REQUIRED",
646
- message: "Waiting for verification...",
551
+ message: promptMessage || (kind === "verification" ? "Waiting for verification..." : "Waiting for your input..."),
647
552
  status: "in_progress",
648
- timestamp: Date.now()
649
- });
650
- state.currentExecutingStepId = retryStepId;
651
- state.lastEventType = eventType;
652
- break;
653
- }
654
- case "USER_ACTION_REJECTED": {
655
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
656
- appendThinkingText(state, "\n" + displayMessage);
657
- completeLastInProgressStep(state.steps);
658
- state.userActionRequest = void 0;
659
- state.userActionPending = false;
660
- state.userActionResult = "rejected";
661
- const stepId = `step-${state.stepCounter++}`;
662
- state.steps.push({
663
- id: stepId,
664
- eventType,
665
- message: displayMessage,
666
- status: "completed",
667
- timestamp: Date.now(),
668
- elapsedMs: event.elapsedMs
669
- });
670
- state.lastEventType = eventType;
671
- break;
672
- }
673
- case "USER_ACTION_EXPIRED": {
674
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
675
- appendThinkingText(state, "\n\u2717 " + displayMessage);
676
- completeLastInProgressStep(state.steps);
677
- state.userActionRequest = void 0;
678
- state.userActionPending = false;
679
- const stepId = `step-${state.stepCounter++}`;
680
- state.steps.push({
681
- id: stepId,
682
- eventType,
683
- message: displayMessage,
684
- status: "error",
685
- timestamp: Date.now(),
686
- elapsedMs: event.elapsedMs
687
- });
688
- state.lastEventType = eventType;
689
- break;
690
- }
691
- case "USER_ACTION_RESENT": {
692
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
693
- appendThinkingText(state, "\n" + displayMessage);
694
- const stepId = `step-${state.stepCounter++}`;
695
- state.steps.push({
696
- id: stepId,
697
- eventType,
698
- message: displayMessage,
699
- status: "completed",
700
553
  timestamp: Date.now(),
701
554
  elapsedMs: event.elapsedMs
702
555
  });
556
+ state.currentExecutingStepId = stepId;
703
557
  state.lastEventType = eventType;
704
558
  break;
705
559
  }
706
- case "USER_ACTION_FAILED": {
707
- const displayMessage = getUserActionDisplayMessage(
708
- eventType,
709
- event.message || event.errorMessage
710
- );
711
- appendThinkingText(state, "\n\u2717 " + displayMessage);
712
- completeLastInProgressStep(state.steps);
713
- state.userActionRequest = void 0;
714
- state.userActionPending = false;
715
- const stepId = `step-${state.stepCounter++}`;
716
- state.steps.push({
717
- id: stepId,
718
- eventType,
719
- message: displayMessage,
720
- status: "error",
721
- timestamp: Date.now(),
722
- elapsedMs: event.elapsedMs
723
- });
560
+ case "USER_NOTIFICATION": {
561
+ const noteMessage = typeof event.message === "string" && event.message.trim() || message || "";
562
+ const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
563
+ if (noteMessage || userActionId) {
564
+ const notification = {
565
+ id: userActionId || `note-${state.stepCounter++}`,
566
+ message: noteMessage
567
+ };
568
+ state.notifications.push(notification);
569
+ state.lastNotification = notification;
570
+ }
724
571
  state.lastEventType = eventType;
725
572
  break;
726
573
  }
727
574
  case "WORKFLOW_ERROR":
728
- case "ERROR":
729
575
  state.hasError = true;
730
576
  state.errorMessage = event.errorMessage || event.message || "Workflow error";
731
577
  state.lastEventType = eventType;
732
578
  break;
733
- case "INTENT_ERROR": {
734
- state.errorMessage = message || event.errorMessage || "An error occurred";
735
- const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
736
- if (intentStep) {
737
- intentStep.status = "error";
738
- intentStep.isThinking = false;
739
- }
740
- state.lastEventType = eventType;
741
- break;
742
- }
743
579
  // ---- K2 pipeline stage lifecycle events ----
744
580
  //
745
581
  // The k2-server playground streaming API emits
@@ -860,96 +696,6 @@ function processStreamEventV2(rawEvent, state) {
860
696
  return state;
861
697
  }
862
698
 
863
- // src/utils/messageStateManager.ts
864
- function buildFormattedThinking(steps, allThinkingText) {
865
- const parts = [];
866
- const safeSteps = steps ?? [];
867
- const cleanAll = allThinkingText.replace(/^\s+/, "");
868
- if (cleanAll) {
869
- const firstStepWithThinking = safeSteps.find(
870
- (s) => s.thinkingText && s.thinkingText.trim()
871
- );
872
- if (!firstStepWithThinking) {
873
- parts.push("**Preflight**");
874
- parts.push(cleanAll);
875
- } else {
876
- const stepText = firstStepWithThinking.thinkingText.trim();
877
- const idx = cleanAll.indexOf(stepText);
878
- if (idx > 0) {
879
- const orphaned = cleanAll.substring(0, idx).replace(/\s+$/, "");
880
- if (orphaned) {
881
- parts.push("**Preflight**");
882
- parts.push(orphaned);
883
- }
884
- }
885
- }
886
- }
887
- for (const step of safeSteps) {
888
- switch (step.eventType) {
889
- case "STAGE_STARTED": {
890
- if (step.message) parts.push(`**${step.message}**`);
891
- break;
892
- }
893
- case "ORCHESTRATOR_THINKING":
894
- parts.push("**Planning**");
895
- if (step.message) parts.push(step.message);
896
- break;
897
- case "INTENT_STARTED": {
898
- let label = step.message || "Processing";
899
- const started = label.match(/^(.+?)\s+started$/i);
900
- const progress = label.match(/^(.+?)\s+in progress$/i);
901
- if (started) label = started[1];
902
- else if (progress) label = progress[1];
903
- parts.push(`**${label}**`);
904
- if (step.thinkingText) parts.push(step.thinkingText);
905
- break;
906
- }
907
- case "INTENT_PROGRESS": {
908
- if (step.thinkingText) parts.push(step.thinkingText);
909
- else if (step.message) parts.push(step.message);
910
- break;
911
- }
912
- case "AGGREGATOR_THINKING":
913
- parts.push("**Finalizing**");
914
- if (step.message) parts.push(step.message);
915
- break;
916
- case "USER_ACTION_REQUIRED":
917
- parts.push("**Verification Required**");
918
- if (step.message) parts.push(step.message);
919
- break;
920
- case "USER_ACTION_SUCCESS":
921
- parts.push(`\u2713 ${step.message || "Verification successful"}`);
922
- break;
923
- case "USER_ACTION_REJECTED":
924
- parts.push(`\u2717 ${step.message || "Verification rejected"}`);
925
- break;
926
- case "USER_ACTION_EXPIRED":
927
- parts.push(`\u2717 ${step.message || "Verification expired"}`);
928
- break;
929
- case "USER_ACTION_FAILED":
930
- parts.push(`\u2717 ${step.message || "Verification failed"}`);
931
- break;
932
- }
933
- }
934
- return parts.length > 0 ? parts.join("\n") : allThinkingText;
935
- }
936
- function createCancelledMessageUpdate(steps, currentMessage) {
937
- const updatedSteps = steps.map((step) => {
938
- if (step.status === "in_progress") {
939
- return { ...step, status: "pending" };
940
- }
941
- return step;
942
- });
943
- return {
944
- isStreaming: false,
945
- isCancelled: true,
946
- steps: updatedSteps,
947
- currentExecutingStepId: void 0,
948
- // Preserve currentMessage so UI can show it with X icon
949
- currentMessage: currentMessage || "Thinking..."
950
- };
951
- }
952
-
953
699
  // src/utils/requestBuilder.ts
954
700
  var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
955
701
  function buildRequestBody(config, userMessage, sessionId, options) {
@@ -1023,111 +769,6 @@ function buildRequestHeaders(config) {
1023
769
  return headers;
1024
770
  }
1025
771
 
1026
- // src/utils/userActionClient.ts
1027
- async function sendUserActionRequest(config, userActionId, action, data) {
1028
- const url = buildUserActionUrl(config, userActionId, action);
1029
- const baseHeaders = buildRequestHeaders(config);
1030
- const hasBody = data !== void 0;
1031
- const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
1032
- const response = await fetch(url, {
1033
- method: "POST",
1034
- headers,
1035
- body: hasBody ? JSON.stringify(data) : void 0
1036
- });
1037
- if (!response.ok) {
1038
- const errorText = await response.text();
1039
- throw new Error(`HTTP ${response.status}: ${errorText}`);
1040
- }
1041
- return await response.json();
1042
- }
1043
- async function submitUserAction(config, userActionId, data) {
1044
- return sendUserActionRequest(config, userActionId, "submit", data);
1045
- }
1046
- async function cancelUserAction(config, userActionId) {
1047
- return sendUserActionRequest(config, userActionId, "cancel");
1048
- }
1049
- async function resendUserAction(config, userActionId) {
1050
- return sendUserActionRequest(config, userActionId, "resend");
1051
- }
1052
-
1053
- // src/utils/chatStore.ts
1054
- var memoryStore = /* @__PURE__ */ new Map();
1055
- var chatStore = {
1056
- get(key) {
1057
- return memoryStore.get(key) ?? [];
1058
- },
1059
- set(key, messages) {
1060
- memoryStore.set(key, messages);
1061
- },
1062
- delete(key) {
1063
- memoryStore.delete(key);
1064
- }
1065
- };
1066
-
1067
- // src/utils/activeStreamStore.ts
1068
- var streams = /* @__PURE__ */ new Map();
1069
- var activeStreamStore = {
1070
- has(key) {
1071
- return streams.has(key);
1072
- },
1073
- get(key) {
1074
- const entry = streams.get(key);
1075
- if (!entry) return null;
1076
- return { messages: entry.messages, isWaiting: entry.isWaiting };
1077
- },
1078
- // Called before startStream — registers the controller and initial messages
1079
- start(key, abortController, initialMessages) {
1080
- const existing = streams.get(key);
1081
- streams.set(key, {
1082
- messages: initialMessages,
1083
- isWaiting: true,
1084
- abortController,
1085
- listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
1086
- });
1087
- },
1088
- // Called by the stream on every event — applies the same updater pattern React uses
1089
- applyMessages(key, updater) {
1090
- const entry = streams.get(key);
1091
- if (!entry) return;
1092
- const next = typeof updater === "function" ? updater(entry.messages) : updater;
1093
- entry.messages = next;
1094
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
1095
- },
1096
- setWaiting(key, waiting) {
1097
- const entry = streams.get(key);
1098
- if (!entry) return;
1099
- entry.isWaiting = waiting;
1100
- entry.listeners.forEach((l) => l(entry.messages, waiting));
1101
- },
1102
- // Called when stream completes — persists to chatStore and cleans up
1103
- complete(key) {
1104
- const entry = streams.get(key);
1105
- if (!entry) return;
1106
- entry.isWaiting = false;
1107
- entry.listeners.forEach((l) => l(entry.messages, false));
1108
- const toSave = entry.messages.filter((m) => !m.isStreaming);
1109
- if (toSave.length > 0) chatStore.set(key, toSave);
1110
- streams.delete(key);
1111
- },
1112
- // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
1113
- subscribe(key, listener) {
1114
- const entry = streams.get(key);
1115
- if (!entry) return () => {
1116
- };
1117
- entry.listeners.add(listener);
1118
- return () => {
1119
- streams.get(key)?.listeners.delete(listener);
1120
- };
1121
- },
1122
- // Explicit user cancel — aborts the controller and removes the entry
1123
- abort(key) {
1124
- const entry = streams.get(key);
1125
- if (!entry) return;
1126
- entry.abortController.abort();
1127
- streams.delete(key);
1128
- }
1129
- };
1130
-
1131
772
  // src/utils/ragImageResolver.ts
1132
773
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
1133
774
  function hasRagImages(content) {
@@ -1215,15 +856,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1215
856
  onEvent: (event) => {
1216
857
  if (abortController.signal.aborted) return;
1217
858
  processStreamEventV2(event, state);
1218
- const eventType = event.eventType;
1219
- if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
1220
- callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
1221
- } else if (typeof eventType === "string" && eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
1222
- const msg = getUserActionDisplayMessage(
1223
- eventType,
1224
- event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
1225
- );
1226
- callbacksRef.current.onUserActionEvent?.(eventType, msg);
859
+ if (state.lastUserAction) {
860
+ callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
861
+ }
862
+ if (state.lastNotification) {
863
+ callbacksRef.current.onUserNotification?.(state.lastNotification);
1227
864
  }
1228
865
  const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
1229
866
  const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
@@ -1246,7 +883,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1246
883
  streamProgress: "error",
1247
884
  isError: true,
1248
885
  errorDetails: state.errorMessage,
1249
- formattedThinkingText: state.formattedThinkingText || void 0,
1250
886
  steps: [...state.steps],
1251
887
  currentExecutingStepId: void 0,
1252
888
  executionId: state.executionId,
@@ -1259,12 +895,10 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1259
895
  currentMessage,
1260
896
  streamProgress: "processing",
1261
897
  isError: false,
1262
- formattedThinkingText: state.formattedThinkingText || void 0,
1263
898
  steps: [...state.steps],
1264
899
  currentExecutingStepId: state.currentExecutingStepId,
1265
900
  executionId: state.executionId,
1266
901
  sessionId: state.sessionId,
1267
- userActionResult: state.userActionResult,
1268
902
  isCancelled: false
1269
903
  });
1270
904
  }
@@ -1275,14 +909,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1275
909
  if (error.name !== "AbortError") {
1276
910
  callbacksRef.current.onError?.(error);
1277
911
  }
1278
- if (state.userActionPending) {
1279
- state.userActionPending = false;
1280
- state.userActionRequest = void 0;
1281
- callbacksRef.current.onUserActionEvent?.(
1282
- "USER_ACTION_FAILED",
1283
- "Connection lost. Please try again."
1284
- );
1285
- }
1286
912
  const isAborted = error.name === "AbortError";
1287
913
  setMessages(
1288
914
  (prev) => prev.map(
@@ -1295,7 +921,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1295
921
  errorDetails: isAborted ? void 0 : error.message,
1296
922
  content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1297
923
  currentMessage: isAborted ? "Thinking..." : void 0,
1298
- formattedThinkingText: state.formattedThinkingText || void 0,
1299
924
  steps: [...state.steps].map((step) => {
1300
925
  if (step.status === "in_progress" && isAborted) {
1301
926
  return { ...step, status: "pending" };
@@ -1311,14 +936,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1311
936
  setIsWaitingForResponse(false);
1312
937
  callbacksRef.current.onStatusMessage?.(null);
1313
938
  callbacksRef.current.onStepsUpdate?.([]);
1314
- if (state.userActionPending) {
1315
- state.userActionPending = false;
1316
- state.userActionRequest = void 0;
1317
- callbacksRef.current.onUserActionEvent?.(
1318
- "USER_ACTION_FAILED",
1319
- "Verification could not be completed."
1320
- );
1321
- }
1322
939
  if (state.sessionId && state.sessionId !== sessionId) {
1323
940
  callbacksRef.current.onSessionIdChange?.(state.sessionId);
1324
941
  }
@@ -1342,8 +959,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1342
959
  steps: state.hasError ? [] : [...state.steps],
1343
960
  isCancelled: false,
1344
961
  currentExecutingStepId: void 0,
1345
- userActionResult: state.userActionResult,
1346
- formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
1347
962
  isResolvingImages: needsImageResolve,
1348
963
  totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
1349
964
  };
@@ -1385,14 +1000,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1385
1000
  if (error.name !== "AbortError") {
1386
1001
  callbacksRef.current.onError?.(error);
1387
1002
  }
1388
- if (state.userActionPending) {
1389
- state.userActionPending = false;
1390
- state.userActionRequest = void 0;
1391
- callbacksRef.current.onUserActionEvent?.(
1392
- "USER_ACTION_FAILED",
1393
- "Connection lost. Please try again."
1394
- );
1395
- }
1396
1003
  const isAborted = error.name === "AbortError";
1397
1004
  setMessages(
1398
1005
  (prev) => prev.map(
@@ -1404,7 +1011,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1404
1011
  isCancelled: isAborted,
1405
1012
  errorDetails: isAborted ? void 0 : error.message,
1406
1013
  content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1407
- formattedThinkingText: state.formattedThinkingText || void 0,
1408
1014
  steps: [...state.steps].map((step) => {
1409
1015
  if (step.status === "in_progress" && isAborted) {
1410
1016
  return { ...step, status: "pending" };
@@ -1430,9 +1036,79 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1430
1036
  };
1431
1037
  }
1432
1038
 
1039
+ // src/utils/messageStateManager.ts
1040
+ function createCancelledMessageUpdate(steps, currentMessage) {
1041
+ const updatedSteps = steps.map((step) => {
1042
+ if (step.status === "in_progress") {
1043
+ return { ...step, status: "pending" };
1044
+ }
1045
+ return step;
1046
+ });
1047
+ return {
1048
+ isStreaming: false,
1049
+ isCancelled: true,
1050
+ steps: updatedSteps,
1051
+ currentExecutingStepId: void 0,
1052
+ currentMessage: currentMessage || "Thinking..."
1053
+ };
1054
+ }
1055
+
1056
+ // src/utils/userActionClient.ts
1057
+ var UserActionStaleError = class extends Error {
1058
+ constructor(userActionId, message = "User action is no longer actionable") {
1059
+ super(message);
1060
+ __publicField(this, "userActionId");
1061
+ this.name = "UserActionStaleError";
1062
+ this.userActionId = userActionId;
1063
+ }
1064
+ };
1065
+ async function sendUserActionRequest(config, userActionId, action, data) {
1066
+ const url = buildUserActionUrl(config, userActionId, action);
1067
+ const baseHeaders = buildRequestHeaders(config);
1068
+ const hasBody = data !== void 0;
1069
+ const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
1070
+ const response = await fetch(url, {
1071
+ method: "POST",
1072
+ headers,
1073
+ body: hasBody ? JSON.stringify(data) : void 0
1074
+ });
1075
+ if (response.status === 404) {
1076
+ throw new UserActionStaleError(userActionId);
1077
+ }
1078
+ if (!response.ok) {
1079
+ const errorText = await response.text();
1080
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1081
+ }
1082
+ return await response.json();
1083
+ }
1084
+ async function submitUserAction(config, userActionId, content) {
1085
+ return sendUserActionRequest(config, userActionId, "submit", content ?? {});
1086
+ }
1087
+ async function cancelUserAction(config, userActionId) {
1088
+ return sendUserActionRequest(config, userActionId, "cancel");
1089
+ }
1090
+ async function resendUserAction(config, userActionId) {
1091
+ return sendUserActionRequest(config, userActionId, "resend");
1092
+ }
1093
+
1433
1094
  // src/hooks/useChatV2.ts
1095
+ var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1096
+ function upsertPrompt(prompts, req) {
1097
+ const active = { ...req, status: "pending" };
1098
+ const idx = prompts.findIndex(
1099
+ (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
1100
+ );
1101
+ if (idx >= 0) {
1102
+ const next = prompts.slice();
1103
+ next[idx] = active;
1104
+ return next;
1105
+ }
1106
+ return [...prompts, active];
1107
+ }
1434
1108
  function getStoredOrInitialMessages(config) {
1435
1109
  if (!config.userId) return config.initialMessages ?? [];
1110
+ const activeStream = activeStreamStore.get(config.userId);
1111
+ if (activeStream) return activeStream.messages;
1436
1112
  const stored = chatStore.get(config.userId);
1437
1113
  if (stored.length > 0) return stored;
1438
1114
  if (config.initialMessages?.length) {
@@ -1446,11 +1122,16 @@ function getSessionIdFromMessages(messages) {
1446
1122
  }
1447
1123
  function useChatV2(config, callbacks = {}) {
1448
1124
  const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
1449
- const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1125
+ const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
1126
+ if (!config.userId) return false;
1127
+ return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1128
+ });
1450
1129
  const sessionIdRef = react.useRef(
1451
1130
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1452
1131
  );
1453
1132
  const prevUserIdRef = react.useRef(config.userId);
1133
+ const streamUserIdRef = react.useRef(void 0);
1134
+ const subscriptionPrevUserIdRef = react.useRef(config.userId);
1454
1135
  const callbacksRef = react.useRef(callbacks);
1455
1136
  callbacksRef.current = callbacks;
1456
1137
  const configRef = react.useRef(config);
@@ -1459,33 +1140,35 @@ function useChatV2(config, callbacks = {}) {
1459
1140
  messagesRef.current = messages;
1460
1141
  const storeAwareSetMessages = react.useCallback(
1461
1142
  (updater) => {
1462
- const { userId } = configRef.current;
1463
- if (userId && activeStreamStore.has(userId)) {
1464
- activeStreamStore.applyMessages(userId, updater);
1143
+ const streamUserId = streamUserIdRef.current;
1144
+ const currentUserId = configRef.current.userId;
1145
+ const storeKey = streamUserId ?? currentUserId;
1146
+ if (storeKey && activeStreamStore.has(storeKey)) {
1147
+ activeStreamStore.applyMessages(storeKey, updater);
1148
+ }
1149
+ if (!streamUserId || streamUserId === currentUserId) {
1150
+ setMessages(updater);
1465
1151
  }
1466
- setMessages(updater);
1467
1152
  },
1468
1153
  // eslint-disable-next-line react-hooks/exhaustive-deps
1469
1154
  []
1470
1155
  );
1471
1156
  const storeAwareSetIsWaiting = react.useCallback(
1472
1157
  (waiting) => {
1473
- const { userId } = configRef.current;
1474
- if (userId && activeStreamStore.has(userId)) {
1475
- activeStreamStore.setWaiting(userId, waiting);
1158
+ const streamUserId = streamUserIdRef.current;
1159
+ const currentUserId = configRef.current.userId;
1160
+ const storeKey = streamUserId ?? currentUserId;
1161
+ if (storeKey && activeStreamStore.has(storeKey)) {
1162
+ activeStreamStore.setWaiting(storeKey, waiting);
1163
+ }
1164
+ if (!streamUserId || streamUserId === currentUserId) {
1165
+ setIsWaitingForResponse(waiting);
1476
1166
  }
1477
- setIsWaitingForResponse(waiting);
1478
1167
  },
1479
1168
  // eslint-disable-next-line react-hooks/exhaustive-deps
1480
1169
  []
1481
1170
  );
1482
- const [userActionState, setUserActionState] = react.useState({
1483
- request: null,
1484
- result: null,
1485
- clearOtpTrigger: 0
1486
- });
1487
- const userActionStateRef = react.useRef(userActionState);
1488
- userActionStateRef.current = userActionState;
1171
+ const [userActionState, setUserActionState] = react.useState(EMPTY_USER_ACTION_STATE);
1489
1172
  const wrappedCallbacks = react.useMemo(() => ({
1490
1173
  ...callbacksRef.current,
1491
1174
  onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
@@ -1495,26 +1178,17 @@ function useChatV2(config, callbacks = {}) {
1495
1178
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1496
1179
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1497
1180
  onUserActionRequired: (request) => {
1498
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1181
+ setUserActionState((prev) => ({
1182
+ ...prev,
1183
+ prompts: upsertPrompt(prev.prompts, request)
1184
+ }));
1499
1185
  callbacksRef.current.onUserActionRequired?.(request);
1500
1186
  },
1501
- onUserActionEvent: (eventType, message) => {
1502
- switch (eventType) {
1503
- case "USER_ACTION_SUCCESS":
1504
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1505
- break;
1506
- case "USER_ACTION_REJECTED":
1507
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1508
- break;
1509
- case "USER_ACTION_EXPIRED":
1510
- case "USER_ACTION_FAILED":
1511
- setUserActionState((prev) => ({ ...prev, request: null }));
1512
- break;
1513
- case "USER_ACTION_INVALID":
1514
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1515
- break;
1516
- }
1517
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1187
+ onUserNotification: (notification) => {
1188
+ setUserActionState(
1189
+ (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1190
+ );
1191
+ callbacksRef.current.onUserNotification?.(notification);
1518
1192
  }
1519
1193
  // eslint-disable-next-line react-hooks/exhaustive-deps
1520
1194
  }), []);
@@ -1562,6 +1236,7 @@ function useChatV2(config, callbacks = {}) {
1562
1236
  const abortController = new AbortController();
1563
1237
  const { userId } = configRef.current;
1564
1238
  if (userId) {
1239
+ streamUserIdRef.current = userId;
1565
1240
  const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1566
1241
  activeStreamStore.start(userId, abortController, initialMessages);
1567
1242
  }
@@ -1572,9 +1247,11 @@ function useChatV2(config, callbacks = {}) {
1572
1247
  abortController,
1573
1248
  options
1574
1249
  );
1575
- if (userId) {
1576
- activeStreamStore.complete(userId);
1250
+ const finalStreamUserId = streamUserIdRef.current ?? userId;
1251
+ if (finalStreamUserId) {
1252
+ activeStreamStore.complete(finalStreamUserId);
1577
1253
  }
1254
+ streamUserIdRef.current = void 0;
1578
1255
  if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1579
1256
  sessionIdRef.current = newSessionId;
1580
1257
  }
@@ -1591,12 +1268,14 @@ function useChatV2(config, callbacks = {}) {
1591
1268
  setMessages((prev) => [...msgs, ...prev]);
1592
1269
  }, []);
1593
1270
  const cancelStream = react.useCallback(() => {
1594
- if (configRef.current.userId) {
1595
- activeStreamStore.abort(configRef.current.userId);
1271
+ const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1272
+ if (streamUserId) {
1273
+ activeStreamStore.abort(streamUserId);
1596
1274
  }
1275
+ streamUserIdRef.current = void 0;
1597
1276
  cancelStreamManager();
1598
1277
  setIsWaitingForResponse(false);
1599
- setUserActionState((prev) => ({ ...prev, request: null, result: null }));
1278
+ setUserActionState((prev) => ({ ...prev, prompts: [] }));
1600
1279
  setMessages(
1601
1280
  (prev) => prev.map((msg) => {
1602
1281
  if (msg.isStreaming) {
@@ -1613,15 +1292,19 @@ function useChatV2(config, callbacks = {}) {
1613
1292
  );
1614
1293
  }, [cancelStreamManager]);
1615
1294
  const resetSession = react.useCallback(() => {
1295
+ const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1296
+ if (streamUserId) {
1297
+ activeStreamStore.abort(streamUserId);
1298
+ }
1616
1299
  if (configRef.current.userId) {
1617
- activeStreamStore.abort(configRef.current.userId);
1618
1300
  chatStore.delete(configRef.current.userId);
1619
1301
  }
1302
+ streamUserIdRef.current = void 0;
1620
1303
  setMessages([]);
1621
1304
  sessionIdRef.current = void 0;
1622
1305
  abortControllerRef.current?.abort();
1623
1306
  setIsWaitingForResponse(false);
1624
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1307
+ setUserActionState(EMPTY_USER_ACTION_STATE);
1625
1308
  }, []);
1626
1309
  const getSessionId = react.useCallback(() => {
1627
1310
  return sessionIdRef.current;
@@ -1629,59 +1312,91 @@ function useChatV2(config, callbacks = {}) {
1629
1312
  const getMessages = react.useCallback(() => {
1630
1313
  return messages;
1631
1314
  }, [messages]);
1632
- const approveUserAction = react.useCallback(
1633
- async (otp) => {
1634
- const request = userActionStateRef.current.request;
1635
- if (!request) return;
1315
+ const setPromptStatus = react.useCallback(
1316
+ (userActionId, status) => {
1317
+ setUserActionState((prev) => ({
1318
+ ...prev,
1319
+ prompts: prev.prompts.map(
1320
+ (p) => p.userActionId === userActionId ? { ...p, status } : p
1321
+ )
1322
+ }));
1323
+ },
1324
+ []
1325
+ );
1326
+ const removePrompt = react.useCallback((userActionId) => {
1327
+ setUserActionState((prev) => ({
1328
+ ...prev,
1329
+ prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
1330
+ }));
1331
+ }, []);
1332
+ const submitUserAction2 = react.useCallback(
1333
+ async (userActionId, content) => {
1334
+ setPromptStatus(userActionId, "submitting");
1636
1335
  try {
1637
- await submitUserAction(configRef.current, request.userActionId, { otp });
1336
+ await submitUserAction(configRef.current, userActionId, content);
1337
+ removePrompt(userActionId);
1638
1338
  } catch (error) {
1639
- setUserActionState((prev) => ({
1640
- ...prev,
1641
- clearOtpTrigger: prev.clearOtpTrigger + 1
1642
- }));
1339
+ if (error instanceof UserActionStaleError) {
1340
+ setPromptStatus(userActionId, "stale");
1341
+ return;
1342
+ }
1343
+ setPromptStatus(userActionId, "pending");
1643
1344
  callbacksRef.current.onError?.(error);
1644
1345
  throw error;
1645
1346
  }
1646
1347
  },
1647
- []
1348
+ [removePrompt, setPromptStatus]
1648
1349
  );
1649
- const rejectUserAction = react.useCallback(async () => {
1650
- const request = userActionStateRef.current.request;
1651
- if (!request) return;
1652
- try {
1653
- setMessages((prev) => {
1654
- let lastStreamingIdx = -1;
1655
- for (let i = prev.length - 1; i >= 0; i--) {
1656
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
1657
- lastStreamingIdx = i;
1658
- break;
1659
- }
1350
+ const cancelUserAction2 = react.useCallback(
1351
+ async (userActionId) => {
1352
+ setPromptStatus(userActionId, "submitting");
1353
+ try {
1354
+ await cancelUserAction(configRef.current, userActionId);
1355
+ removePrompt(userActionId);
1356
+ } catch (error) {
1357
+ if (error instanceof UserActionStaleError) {
1358
+ removePrompt(userActionId);
1359
+ return;
1660
1360
  }
1661
- if (lastStreamingIdx === -1) return prev;
1662
- return prev.map(
1663
- (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
1664
- );
1665
- });
1666
- await cancelUserAction(configRef.current, request.userActionId);
1667
- } catch (error) {
1668
- callbacksRef.current.onError?.(error);
1669
- throw error;
1670
- }
1671
- }, []);
1672
- const resendOtp = react.useCallback(async () => {
1673
- const request = userActionStateRef.current.request;
1674
- if (!request) return;
1675
- try {
1676
- await resendUserAction(configRef.current, request.userActionId);
1677
- } catch (error) {
1678
- callbacksRef.current.onError?.(error);
1679
- throw error;
1680
- }
1361
+ setPromptStatus(userActionId, "pending");
1362
+ callbacksRef.current.onError?.(error);
1363
+ throw error;
1364
+ }
1365
+ },
1366
+ [removePrompt, setPromptStatus]
1367
+ );
1368
+ const resendUserAction2 = react.useCallback(
1369
+ async (userActionId) => {
1370
+ setPromptStatus(userActionId, "submitting");
1371
+ try {
1372
+ await resendUserAction(configRef.current, userActionId);
1373
+ setPromptStatus(userActionId, "pending");
1374
+ } catch (error) {
1375
+ if (error instanceof UserActionStaleError) {
1376
+ setPromptStatus(userActionId, "stale");
1377
+ return;
1378
+ }
1379
+ setPromptStatus(userActionId, "pending");
1380
+ callbacksRef.current.onError?.(error);
1381
+ throw error;
1382
+ }
1383
+ },
1384
+ [setPromptStatus]
1385
+ );
1386
+ const dismissNotification = react.useCallback((id) => {
1387
+ setUserActionState((prev) => ({
1388
+ ...prev,
1389
+ notifications: prev.notifications.filter((n) => n.id !== id)
1390
+ }));
1681
1391
  }, []);
1682
1392
  react.useEffect(() => {
1393
+ const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
1394
+ subscriptionPrevUserIdRef.current = config.userId;
1683
1395
  const { userId } = config;
1684
1396
  if (!userId) return;
1397
+ if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
1398
+ streamUserIdRef.current = userId;
1399
+ }
1685
1400
  const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1686
1401
  setMessages(msgs);
1687
1402
  setIsWaitingForResponse(isWaiting);
@@ -1695,6 +1410,7 @@ function useChatV2(config, callbacks = {}) {
1695
1410
  }, [config.userId]);
1696
1411
  react.useEffect(() => {
1697
1412
  if (!config.userId) return;
1413
+ if (prevUserIdRef.current !== config.userId) return;
1698
1414
  const toSave = messages.filter((m) => !m.isStreaming);
1699
1415
  if (toSave.length > 0) {
1700
1416
  chatStore.set(config.userId, toSave);
@@ -1716,7 +1432,7 @@ function useChatV2(config, callbacks = {}) {
1716
1432
  setMessages([]);
1717
1433
  sessionIdRef.current = void 0;
1718
1434
  setIsWaitingForResponse(false);
1719
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1435
+ setUserActionState(EMPTY_USER_ACTION_STATE);
1720
1436
  } else if (config.userId) {
1721
1437
  const active = activeStreamStore.get(config.userId);
1722
1438
  if (active) {
@@ -1743,9 +1459,10 @@ function useChatV2(config, callbacks = {}) {
1743
1459
  isWaitingForResponse,
1744
1460
  sessionId: sessionIdRef.current,
1745
1461
  userActionState,
1746
- approveUserAction,
1747
- rejectUserAction,
1748
- resendOtp
1462
+ submitUserAction: submitUserAction2,
1463
+ cancelUserAction: cancelUserAction2,
1464
+ resendUserAction: resendUserAction2,
1465
+ dismissNotification
1749
1466
  };
1750
1467
  }
1751
1468
  function getSpeechRecognition() {
@@ -1964,15 +1681,150 @@ function useVoice(config = {}, callbacks = {}) {
1964
1681
  };
1965
1682
  }
1966
1683
 
1967
- exports.buildFormattedThinking = buildFormattedThinking;
1684
+ // src/utils/jsonSchemaForm.ts
1685
+ function classifyField(field) {
1686
+ if (!field) return "text";
1687
+ if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
1688
+ switch (field.type) {
1689
+ case "boolean":
1690
+ return "boolean";
1691
+ case "integer":
1692
+ return "integer";
1693
+ case "number":
1694
+ return "decimal";
1695
+ case "string":
1696
+ return "text";
1697
+ default:
1698
+ return "text";
1699
+ }
1700
+ }
1701
+ function isNestedOrUnsupported(field) {
1702
+ if (!field) return false;
1703
+ if (field.type === "object" || field.type === "array") return true;
1704
+ if ("properties" in field && field.properties != null) return true;
1705
+ if ("items" in field && field.items != null) return true;
1706
+ return false;
1707
+ }
1708
+ function getOptions(field) {
1709
+ if (!field || !Array.isArray(field.oneOf)) return [];
1710
+ return field.oneOf.filter(
1711
+ (o) => !!o && typeof o === "object" && typeof o.const === "string"
1712
+ );
1713
+ }
1714
+ function isRequired(schema, key) {
1715
+ return Array.isArray(schema?.required) && schema.required.includes(key);
1716
+ }
1717
+ function coerceValue(field, raw) {
1718
+ const widget = classifyField(field);
1719
+ if (widget === "boolean") {
1720
+ if (typeof raw === "boolean") return raw;
1721
+ if (raw === "true") return true;
1722
+ if (raw === "false") return false;
1723
+ return Boolean(raw);
1724
+ }
1725
+ if (widget === "integer" || widget === "decimal") {
1726
+ if (raw === "" || raw == null) return void 0;
1727
+ const num = typeof raw === "number" ? raw : Number(String(raw).trim());
1728
+ if (Number.isNaN(num)) return void 0;
1729
+ return widget === "integer" ? Math.trunc(num) : num;
1730
+ }
1731
+ if (raw == null) return void 0;
1732
+ const str = String(raw);
1733
+ return str === "" ? void 0 : str;
1734
+ }
1735
+ function defaultValueFor(field) {
1736
+ if (!field || field.default === void 0) {
1737
+ return classifyField(field) === "boolean" ? false : "";
1738
+ }
1739
+ return field.default;
1740
+ }
1741
+ function validateField(field, value, required) {
1742
+ const widget = classifyField(field);
1743
+ const label = field?.title || "This field";
1744
+ const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
1745
+ if (isEmpty) {
1746
+ if (required && widget !== "boolean") return `${label} is required.`;
1747
+ return null;
1748
+ }
1749
+ if (widget === "integer" || widget === "decimal") {
1750
+ const num = typeof value === "number" ? value : Number(value);
1751
+ if (Number.isNaN(num)) return `${label} must be a number.`;
1752
+ if (widget === "integer" && !Number.isInteger(num)) {
1753
+ return `${label} must be a whole number.`;
1754
+ }
1755
+ if (typeof field?.minimum === "number" && num < field.minimum) {
1756
+ return `${label} must be at least ${field.minimum}.`;
1757
+ }
1758
+ if (typeof field?.maximum === "number" && num > field.maximum) {
1759
+ return `${label} must be at most ${field.maximum}.`;
1760
+ }
1761
+ return null;
1762
+ }
1763
+ if (widget === "select") {
1764
+ const allowed = getOptions(field).map((o) => o.const);
1765
+ if (allowed.length > 0 && !allowed.includes(String(value))) {
1766
+ return `${label} has an invalid selection.`;
1767
+ }
1768
+ return null;
1769
+ }
1770
+ const str = String(value);
1771
+ if (typeof field?.minLength === "number" && str.length < field.minLength) {
1772
+ return `${label} must be at least ${field.minLength} characters.`;
1773
+ }
1774
+ if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
1775
+ return `${label} must be at most ${field.maxLength} characters.`;
1776
+ }
1777
+ return null;
1778
+ }
1779
+ function renderableFields(schema) {
1780
+ const props = schema?.properties;
1781
+ if (!props) return [];
1782
+ return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
1783
+ }
1784
+ function validateForm(schema, values) {
1785
+ const errors = {};
1786
+ for (const [key, field] of renderableFields(schema)) {
1787
+ const coerced = coerceValue(field, values[key]);
1788
+ const err = validateField(field, coerced, isRequired(schema, key));
1789
+ if (err) errors[key] = err;
1790
+ }
1791
+ return errors;
1792
+ }
1793
+ function buildContent(schema, values) {
1794
+ const content = {};
1795
+ for (const [key, field] of renderableFields(schema)) {
1796
+ const coerced = coerceValue(field, values[key]);
1797
+ if (coerced !== void 0) content[key] = coerced;
1798
+ }
1799
+ return content;
1800
+ }
1801
+
1802
+ // src/index.ts
1803
+ function migrateActiveStream(oldUserId, newUserId) {
1804
+ activeStreamStore.rename(oldUserId, newUserId);
1805
+ }
1806
+
1807
+ exports.UserActionStaleError = UserActionStaleError;
1808
+ exports.buildContent = buildContent;
1968
1809
  exports.cancelUserAction = cancelUserAction;
1810
+ exports.classifyField = classifyField;
1811
+ exports.classifyUserActionKind = classifyUserActionKind;
1812
+ exports.coerceValue = coerceValue;
1969
1813
  exports.createInitialV2State = createInitialV2State;
1814
+ exports.defaultValueFor = defaultValueFor;
1970
1815
  exports.generateId = generateId;
1816
+ exports.getOptions = getOptions;
1817
+ exports.isNestedOrUnsupported = isNestedOrUnsupported;
1818
+ exports.isRequired = isRequired;
1819
+ exports.migrateActiveStream = migrateActiveStream;
1971
1820
  exports.processStreamEventV2 = processStreamEventV2;
1821
+ exports.renderableFields = renderableFields;
1972
1822
  exports.resendUserAction = resendUserAction;
1973
1823
  exports.streamWorkflowEvents = streamWorkflowEvents;
1974
1824
  exports.submitUserAction = submitUserAction;
1975
1825
  exports.useChatV2 = useChatV2;
1976
1826
  exports.useVoice = useVoice;
1827
+ exports.validateField = validateField;
1828
+ exports.validateForm = validateForm;
1977
1829
  //# sourceMappingURL=index.js.map
1978
1830
  //# sourceMappingURL=index.js.map