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