@paymanai/payman-typescript-ask-sdk 1.2.11 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ var react = require('react');
4
4
  var reactNativeMmkv = require('react-native-mmkv');
5
5
  var expoSpeechRecognition = require('expo-speech-recognition');
6
6
 
7
- // src/hooks/useChat.ts
7
+ // src/hooks/useChatV2.ts
8
8
 
9
9
  // src/utils/generateId.ts
10
10
  function generateId() {
@@ -173,13 +173,13 @@ function getEventMessage(event) {
173
173
  case "USER_ACTION_REQUIRED":
174
174
  return "Waiting for verification...";
175
175
  case "USER_ACTION_SUCCESS":
176
- return "Verification successful";
176
+ return "Verification approved";
177
177
  case "USER_ACTION_EXPIRED":
178
178
  return "Verification expired";
179
179
  case "USER_ACTION_INVALID":
180
- return "Invalid input, please try again";
180
+ return "Invalid code. Please try again.";
181
181
  case "USER_ACTION_REJECTED":
182
- return "Verification rejected";
182
+ return "Verification cancelled";
183
183
  case "USER_ACTION_RESENT":
184
184
  return "Verification code resent";
185
185
  case "USER_ACTION_FAILED":
@@ -194,6 +194,31 @@ function getEventMessage(event) {
194
194
  return eventType;
195
195
  }
196
196
  }
197
+ function isUserActionPrompt(text) {
198
+ 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);
199
+ }
200
+ function getUserActionDisplayMessage(eventType, rawMessage) {
201
+ const raw = rawMessage?.trim();
202
+ const safeRaw = raw && !isUserActionPrompt(raw) ? raw : "";
203
+ switch (eventType) {
204
+ case "USER_ACTION_REQUIRED":
205
+ return raw || "Waiting for verification...";
206
+ case "USER_ACTION_SUCCESS":
207
+ return safeRaw || "Verification approved";
208
+ case "USER_ACTION_INVALID":
209
+ return safeRaw || "Invalid code. Please try again.";
210
+ case "USER_ACTION_REJECTED":
211
+ return safeRaw || "Verification cancelled";
212
+ case "USER_ACTION_EXPIRED":
213
+ return safeRaw || "Verification expired";
214
+ case "USER_ACTION_RESENT":
215
+ return safeRaw || "Verification code resent";
216
+ case "USER_ACTION_FAILED":
217
+ return safeRaw || "Verification failed";
218
+ default:
219
+ return safeRaw || raw || eventType;
220
+ }
221
+ }
197
222
  function workingPhaseDetailForDisplay(raw) {
198
223
  const t = raw.trim();
199
224
  if (!t) return "";
@@ -224,290 +249,8 @@ function extractResponseContent(response) {
224
249
  }
225
250
  return "";
226
251
  }
227
- function completeLastInProgressStep(steps) {
228
- for (let i = steps.length - 1; i >= 0; i--) {
229
- if (steps[i].status === "in_progress") {
230
- steps[i].status = "completed";
231
- return;
232
- }
233
- }
234
- }
235
- function processStreamEvent(event, state) {
236
- const eventType = event.eventType;
237
- if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
238
- return state;
239
- }
240
- const message = getEventMessage(event);
241
- if (eventType !== "INTENT_THINKING" && eventType !== "INTENT_THINKING_CONT") {
242
- if (state.currentThinkingStepId) {
243
- const thinkingStep = state.steps.find((s) => s.id === state.currentThinkingStepId);
244
- if (thinkingStep) {
245
- thinkingStep.isThinking = false;
246
- }
247
- state.currentThinkingStepId = void 0;
248
- }
249
- state.activeThinkingText = void 0;
250
- }
251
- if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
252
- let content = extractResponseContent(event.response);
253
- const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
254
- if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
255
- content = trace.workflowMsg;
256
- }
257
- if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
258
- const agg = trace.aggregator;
259
- if (typeof agg.response === "string") content = agg.response;
260
- else content = extractResponseContent(agg.response);
261
- }
262
- if (content) {
263
- state.accumulatedContent = content;
264
- state.finalData = event.response ?? event.trace;
265
- state.hasError = false;
266
- state.errorMessage = "";
267
- } else {
268
- state.hasError = true;
269
- state.errorMessage = "WORKFLOW_FAILED";
270
- }
271
- }
272
- if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
273
- state.steps.forEach((step) => {
274
- if (step.status === "in_progress") {
275
- step.status = "completed";
276
- }
277
- });
278
- } else if (eventType === "INTENT_ERROR") {
279
- state.errorMessage = message || event.errorMessage || "An error occurred";
280
- const intentStep = state.steps.find(
281
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
282
- );
283
- if (intentStep) {
284
- intentStep.status = "error";
285
- }
286
- } else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
287
- state.hasError = true;
288
- state.errorMessage = message || event.errorMessage || "An error occurred";
289
- } else if (eventType === "ORCHESTRATOR_COMPLETED") {
290
- state.inOrchestratorPhase = false;
291
- const orchestratorStep = state.steps.find(
292
- (s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
293
- );
294
- if (orchestratorStep) {
295
- orchestratorStep.status = "completed";
296
- if (event.elapsedMs) {
297
- orchestratorStep.elapsedMs = event.elapsedMs;
298
- }
299
- if (orchestratorStep.id === state.currentExecutingStepId) {
300
- state.currentExecutingStepId = void 0;
301
- }
302
- }
303
- } else if (eventType === "INTENT_COMPLETED") {
304
- const intentStep = state.steps.find(
305
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
306
- );
307
- if (intentStep) {
308
- intentStep.status = "completed";
309
- if (event.elapsedMs) {
310
- intentStep.elapsedMs = event.elapsedMs;
311
- }
312
- if (intentStep.id === state.currentExecutingStepId) {
313
- state.currentExecutingStepId = void 0;
314
- }
315
- }
316
- } else if (eventType === "AGGREGATOR_COMPLETED") {
317
- state.inAggregatorPhase = false;
318
- const aggregatorStep = state.steps.find(
319
- (s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
320
- );
321
- if (aggregatorStep) {
322
- aggregatorStep.status = "completed";
323
- if (event.elapsedMs) {
324
- aggregatorStep.elapsedMs = event.elapsedMs;
325
- }
326
- if (aggregatorStep.id === state.currentExecutingStepId) {
327
- state.currentExecutingStepId = void 0;
328
- }
329
- }
330
- } else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
331
- if (eventType === "ORCHESTRATOR_THINKING") {
332
- state.inOrchestratorPhase = true;
333
- }
334
- if (eventType === "AGGREGATOR_THINKING") {
335
- state.inAggregatorPhase = true;
336
- }
337
- if (eventType === "INTENT_PROGRESS") {
338
- const intentStep = state.steps.find(
339
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
340
- );
341
- if (intentStep) {
342
- intentStep.message = message;
343
- state.currentExecutingStepId = intentStep.id;
344
- } else {
345
- const stepId = `step-${state.stepCounter++}`;
346
- state.steps.push({
347
- id: stepId,
348
- eventType: "INTENT_STARTED",
349
- message,
350
- status: "in_progress",
351
- timestamp: Date.now(),
352
- elapsedMs: event.elapsedMs
353
- });
354
- state.currentExecutingStepId = stepId;
355
- }
356
- } else {
357
- const stepId = `step-${state.stepCounter++}`;
358
- state.steps.push({
359
- id: stepId,
360
- eventType,
361
- message,
362
- status: "in_progress",
363
- timestamp: Date.now(),
364
- elapsedMs: event.elapsedMs
365
- });
366
- state.currentExecutingStepId = stepId;
367
- }
368
- } else if (eventType === "USER_ACTION_REQUIRED") {
369
- completeLastInProgressStep(state.steps);
370
- if (event.userActionRequest) {
371
- state.userActionRequest = {
372
- userActionId: event.userActionRequest.userActionId,
373
- userActionType: event.userActionRequest.userActionType,
374
- message: event.userActionRequest.message,
375
- requestedSchema: event.userActionRequest.requestedSchema,
376
- metadata: event.userActionRequest.metadata
377
- };
378
- }
379
- state.userActionPending = true;
380
- const stepId = `step-${state.stepCounter++}`;
381
- state.steps.push({
382
- id: stepId,
383
- eventType,
384
- message,
385
- status: "in_progress",
386
- timestamp: Date.now(),
387
- elapsedMs: event.elapsedMs
388
- });
389
- state.currentExecutingStepId = stepId;
390
- } else if (eventType === "USER_ACTION_SUCCESS") {
391
- completeLastInProgressStep(state.steps);
392
- state.userActionRequest = void 0;
393
- state.userActionPending = false;
394
- state.userActionResult = "approved";
395
- const stepId = `step-${state.stepCounter++}`;
396
- state.steps.push({
397
- id: stepId,
398
- eventType,
399
- message,
400
- status: "completed",
401
- timestamp: Date.now(),
402
- elapsedMs: event.elapsedMs
403
- });
404
- } else if (eventType === "USER_ACTION_INVALID") {
405
- completeLastInProgressStep(state.steps);
406
- const errorStepId = `step-${state.stepCounter++}`;
407
- state.steps.push({
408
- id: errorStepId,
409
- eventType,
410
- message,
411
- status: "error",
412
- timestamp: Date.now(),
413
- elapsedMs: event.elapsedMs
414
- });
415
- const retryStepId = `step-${state.stepCounter++}`;
416
- state.steps.push({
417
- id: retryStepId,
418
- eventType: "USER_ACTION_REQUIRED",
419
- message: "Waiting for verification...",
420
- status: "in_progress",
421
- timestamp: Date.now()
422
- });
423
- state.currentExecutingStepId = retryStepId;
424
- } else if (eventType === "USER_ACTION_EXPIRED") {
425
- completeLastInProgressStep(state.steps);
426
- state.userActionRequest = void 0;
427
- state.userActionPending = false;
428
- const stepId = `step-${state.stepCounter++}`;
429
- state.steps.push({
430
- id: stepId,
431
- eventType,
432
- message,
433
- status: "error",
434
- timestamp: Date.now(),
435
- elapsedMs: event.elapsedMs
436
- });
437
- } else if (eventType === "USER_ACTION_REJECTED") {
438
- completeLastInProgressStep(state.steps);
439
- state.userActionRequest = void 0;
440
- state.userActionPending = false;
441
- state.userActionResult = "rejected";
442
- const stepId = `step-${state.stepCounter++}`;
443
- state.steps.push({
444
- id: stepId,
445
- eventType,
446
- message,
447
- status: "completed",
448
- timestamp: Date.now(),
449
- elapsedMs: event.elapsedMs
450
- });
451
- } else if (eventType === "USER_ACTION_RESENT") {
452
- const stepId = `step-${state.stepCounter++}`;
453
- state.steps.push({
454
- id: stepId,
455
- eventType,
456
- message,
457
- status: "completed",
458
- timestamp: Date.now(),
459
- elapsedMs: event.elapsedMs
460
- });
461
- } else if (eventType === "USER_ACTION_FAILED") {
462
- completeLastInProgressStep(state.steps);
463
- state.userActionRequest = void 0;
464
- state.userActionPending = false;
465
- const stepId = `step-${state.stepCounter++}`;
466
- state.steps.push({
467
- id: stepId,
468
- eventType,
469
- message,
470
- status: "error",
471
- timestamp: Date.now(),
472
- elapsedMs: event.elapsedMs
473
- });
474
- } else if (eventType === "INTENT_THINKING") {
475
- if (state.currentThinkingStepId) {
476
- const prev = state.steps.find((s) => s.id === state.currentThinkingStepId);
477
- if (prev) prev.isThinking = false;
478
- }
479
- const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
480
- if (lastInProgress) {
481
- lastInProgress.thinkingText = "";
482
- lastInProgress.isThinking = true;
483
- state.currentThinkingStepId = lastInProgress.id;
484
- } else {
485
- state.currentThinkingStepId = void 0;
486
- }
487
- if (state.allThinkingText) state.allThinkingText += "\n\n";
488
- if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
489
- state.activeThinkingText = "";
490
- }
491
- } else if (eventType === "INTENT_THINKING_CONT") {
492
- const delta = event.message || "";
493
- if (!delta) return state;
494
- if (state.currentThinkingStepId) {
495
- const step = state.steps.find((s) => s.id === state.currentThinkingStepId);
496
- if (step) {
497
- step.thinkingText = (step.thinkingText || "") + delta;
498
- }
499
- }
500
- state.allThinkingText += delta;
501
- if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
502
- if (state.activeThinkingText == null) state.activeThinkingText = "";
503
- state.activeThinkingText += delta;
504
- }
505
- }
506
- return state;
507
- }
508
252
 
509
253
  // src/utils/messageStateManager.ts
510
- var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
511
254
  function buildFormattedThinking(steps, allThinkingText) {
512
255
  const parts = [];
513
256
  const safeSteps = steps ?? [];
@@ -564,91 +307,32 @@ function buildFormattedThinking(steps, allThinkingText) {
564
307
  break;
565
308
  case "USER_ACTION_REQUIRED":
566
309
  parts.push("**Verification Required**");
567
- if (step.message) parts.push(step.message);
310
+ if (step.message) {
311
+ parts.push(getUserActionDisplayMessage(step.eventType, step.message));
312
+ }
568
313
  break;
569
314
  case "USER_ACTION_SUCCESS":
570
- parts.push(`\u2713 ${step.message || "Verification successful"}`);
315
+ parts.push(`\u2713 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
571
316
  break;
572
317
  case "USER_ACTION_REJECTED":
573
- parts.push(`\u2717 ${step.message || "Verification rejected"}`);
318
+ parts.push(getUserActionDisplayMessage(step.eventType, step.message));
574
319
  break;
575
320
  case "USER_ACTION_EXPIRED":
576
- parts.push(`\u2717 ${step.message || "Verification expired"}`);
321
+ parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
322
+ break;
323
+ case "USER_ACTION_INVALID":
324
+ parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
325
+ break;
326
+ case "USER_ACTION_RESENT":
327
+ parts.push(getUserActionDisplayMessage(step.eventType, step.message));
577
328
  break;
578
329
  case "USER_ACTION_FAILED":
579
- parts.push(`\u2717 ${step.message || "Verification failed"}`);
330
+ parts.push(`\u2717 ${getUserActionDisplayMessage(step.eventType, step.message)}`);
580
331
  break;
581
332
  }
582
333
  }
583
334
  return parts.length > 0 ? parts.join("\n") : allThinkingText;
584
335
  }
585
- function createStreamingMessageUpdate(state) {
586
- const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
587
- const steps = state.hasError ? [] : [...state.steps];
588
- const allThinking = state.hasError ? void 0 : state.allThinkingText;
589
- return {
590
- streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
591
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
592
- currentMessage: state.hasError ? void 0 : state.currentMessage,
593
- streamProgress: state.hasError ? "error" : "processing",
594
- isError: state.hasError,
595
- errorDetails: state.hasError ? state.errorMessage : void 0,
596
- executionId: state.executionId,
597
- sessionId: state.sessionId,
598
- steps,
599
- currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
600
- isCancelled: false,
601
- userActionResult: state.userActionResult,
602
- activeThinkingText: state.hasError ? void 0 : state.activeThinkingText,
603
- allThinkingText: allThinking,
604
- formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || "")
605
- };
606
- }
607
- function createErrorMessageUpdate(error, state) {
608
- const isAborted = error.name === "AbortError";
609
- return {
610
- isStreaming: false,
611
- streamProgress: isAborted ? "processing" : "error",
612
- isError: !isAborted,
613
- isCancelled: isAborted,
614
- errorDetails: isAborted ? void 0 : error.message,
615
- content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
616
- // Preserve currentMessage when cancelled so UI can show it
617
- currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
618
- steps: [...state.steps].map((step) => {
619
- if (step.status === "in_progress" && isAborted) {
620
- return { ...step, status: "pending" };
621
- }
622
- return step;
623
- }),
624
- currentExecutingStepId: void 0
625
- };
626
- }
627
- function createFinalMessage(streamingId, state) {
628
- const steps = state.hasError ? [] : [...state.steps];
629
- const allThinking = state.hasError ? void 0 : state.allThinkingText;
630
- return {
631
- id: streamingId,
632
- sessionId: state.sessionId,
633
- role: "assistant",
634
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
635
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
636
- isStreaming: false,
637
- streamProgress: state.hasError ? "error" : "completed",
638
- isError: state.hasError,
639
- errorDetails: state.hasError ? state.errorMessage : void 0,
640
- executionId: state.executionId,
641
- tracingData: state.finalData,
642
- steps,
643
- isCancelled: false,
644
- currentExecutingStepId: void 0,
645
- userActionResult: state.userActionResult,
646
- activeThinkingText: void 0,
647
- allThinkingText: allThinking,
648
- formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || ""),
649
- isResolvingImages: state.hasError ? void 0 : state.isResolvingImages
650
- };
651
- }
652
336
  function createCancelledMessageUpdate(steps, currentMessage) {
653
337
  const updatedSteps = steps.map((step) => {
654
338
  if (step.status === "in_progress") {
@@ -668,35 +352,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
668
352
 
669
353
  // src/utils/requestBuilder.ts
670
354
  function buildRequestBody(config, userMessage, sessionId) {
671
- const sessionOwner = config.sessionParams;
672
- const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
355
+ const owner = config.session?.owner;
356
+ const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
673
357
  return {
674
- workflowName: config.workflowName,
358
+ workflowName: config.workflow.name,
675
359
  userInput: userMessage,
676
360
  sessionId,
677
- sessionOwnerId: sessionOwner?.id || "",
678
- sessionOwnerLabel: sessionOwner?.name || "",
361
+ sessionOwnerId: owner?.id || "",
362
+ sessionOwnerLabel: owner?.name || "",
679
363
  sessionAttributes,
680
364
  options: {
681
365
  clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
682
366
  }
683
367
  };
684
368
  }
369
+ function getStageParamName(config) {
370
+ return config.api.stageQueryParam ?? "stage";
371
+ }
372
+ function getStage(config) {
373
+ return config.workflow.stage ?? "DEV";
374
+ }
685
375
  function buildStreamingUrl(config) {
686
376
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
687
- const stage = config.stage || "DEV";
688
- const stageParamName = config.stageQueryParam ?? "stage";
689
- const queryParams = new URLSearchParams({ [stageParamName]: stage });
690
- if (config.workflowVersion !== void 0) {
691
- queryParams.append("workflowVersion", String(config.workflowVersion));
377
+ const queryParams = new URLSearchParams({
378
+ [getStageParamName(config)]: getStage(config)
379
+ });
380
+ if (config.workflow.version !== void 0) {
381
+ queryParams.append("workflowVersion", String(config.workflow.version));
692
382
  }
693
383
  return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
694
384
  }
695
- function buildUserActionUrl(config, userActionId, action) {
385
+ function deriveBasePath(config) {
696
386
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
697
387
  const [endpointPath] = endpoint.split("?");
698
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
699
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
388
+ const normalized = endpointPath.replace(/\/+$/, "");
389
+ return normalized.endsWith("/stream") ? normalized.slice(0, -"/stream".length) : normalized;
390
+ }
391
+ function buildUserActionUrl(config, userActionId, action) {
392
+ const basePath = deriveBasePath(config);
700
393
  const encodedUserActionId = encodeURIComponent(userActionId);
701
394
  return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
702
395
  }
@@ -704,21 +397,23 @@ function buildResolveImagesUrl(config) {
704
397
  if (config.api.resolveImagesEndpoint) {
705
398
  return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
706
399
  }
707
- const streamEndpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
708
- const [endpointPath] = streamEndpoint.split("?");
709
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
710
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
711
- return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
400
+ return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
712
401
  }
713
402
  function buildRequestHeaders(config) {
714
- const headers = {
715
- ...config.api.headers
716
- };
403
+ const headers = { ...config.api.headers };
717
404
  if (config.api.authToken) {
718
405
  headers.Authorization = `Bearer ${config.api.authToken}`;
719
406
  }
720
407
  return headers;
721
408
  }
409
+ function buildScopeKey(config) {
410
+ return [
411
+ config.session?.userId ?? "",
412
+ config.workflow.id ?? "",
413
+ config.workflow.version ?? "",
414
+ config.workflow.stage ?? ""
415
+ ].join("|");
416
+ }
722
417
 
723
418
  // src/utils/userActionClient.ts
724
419
  async function sendUserActionRequest(config, userActionId, action, data) {
@@ -737,684 +432,97 @@ async function sendUserActionRequest(config, userActionId, action, data) {
737
432
  }
738
433
  return await response.json();
739
434
  }
740
- async function submitUserAction(config, userActionId, data) {
741
- return sendUserActionRequest(config, userActionId, "submit", data);
742
- }
743
- async function cancelUserAction(config, userActionId) {
744
- return sendUserActionRequest(config, userActionId, "cancel");
745
- }
746
- async function resendUserAction(config, userActionId) {
747
- return sendUserActionRequest(config, userActionId, "resend");
748
- }
749
- var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
750
- var chatStore = {
751
- get(key) {
752
- const raw = storage.getString(key);
753
- if (!raw) return [];
754
- try {
755
- return JSON.parse(raw);
756
- } catch {
757
- return [];
758
- }
759
- },
760
- set(key, messages) {
761
- storage.set(key, JSON.stringify(messages));
762
- },
763
- delete(key) {
764
- storage.delete(key);
765
- }
766
- };
767
-
768
- // src/utils/activeStreamStore.ts
769
- var streams = /* @__PURE__ */ new Map();
770
- var activeStreamStore = {
771
- has(key) {
772
- return streams.has(key);
773
- },
774
- get(key) {
775
- const entry = streams.get(key);
776
- if (!entry) return null;
777
- return { messages: entry.messages, isWaiting: entry.isWaiting };
778
- },
779
- // Called before startStream — registers the controller and initial messages
780
- start(key, abortController, initialMessages) {
781
- const existing = streams.get(key);
782
- streams.set(key, {
783
- messages: initialMessages,
784
- isWaiting: true,
785
- abortController,
786
- listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
787
- });
788
- },
789
- // Called by the stream on every event — applies the same updater pattern React uses
790
- applyMessages(key, updater) {
791
- const entry = streams.get(key);
792
- if (!entry) return;
793
- const next = typeof updater === "function" ? updater(entry.messages) : updater;
794
- entry.messages = next;
795
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
796
- },
797
- setWaiting(key, waiting) {
798
- const entry = streams.get(key);
799
- if (!entry) return;
800
- entry.isWaiting = waiting;
801
- entry.listeners.forEach((l) => l(entry.messages, waiting));
802
- },
803
- // Called when stream completes — persists to chatStore and cleans up
804
- complete(key) {
805
- const entry = streams.get(key);
806
- if (!entry) return;
807
- entry.isWaiting = false;
808
- entry.listeners.forEach((l) => l(entry.messages, false));
809
- const toSave = entry.messages.filter((m) => !m.isStreaming);
810
- if (toSave.length > 0) chatStore.set(key, toSave);
811
- streams.delete(key);
812
- },
813
- // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
814
- subscribe(key, listener) {
815
- const entry = streams.get(key);
816
- if (!entry) return () => {
817
- };
818
- entry.listeners.add(listener);
819
- return () => {
820
- streams.get(key)?.listeners.delete(listener);
821
- };
822
- },
823
- // Explicit user cancel — aborts the controller and removes the entry
824
- abort(key) {
825
- const entry = streams.get(key);
826
- if (!entry) return;
827
- entry.abortController.abort();
828
- streams.delete(key);
829
- }
830
- };
831
-
832
- // src/utils/ragImageResolver.ts
833
- var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
834
- function hasRagImages(content) {
835
- return RAG_IMAGE_REGEX.test(content);
836
- }
837
- async function waitForNextPaint(signal) {
838
- if (signal?.aborted) return;
839
- await new Promise((resolve) => {
840
- let isSettled = false;
841
- const finish = () => {
842
- if (isSettled) return;
843
- isSettled = true;
844
- signal?.removeEventListener("abort", finish);
845
- resolve();
846
- };
847
- signal?.addEventListener("abort", finish, { once: true });
848
- if (typeof requestAnimationFrame === "function") {
849
- requestAnimationFrame(() => {
850
- setTimeout(finish, 0);
851
- });
852
- return;
853
- }
854
- setTimeout(finish, 0);
855
- });
856
- }
857
- async function resolveRagImageUrls(config, content, signal) {
858
- const url = buildResolveImagesUrl(config);
859
- const baseHeaders = buildRequestHeaders(config);
860
- const headers = { "Content-Type": "application/json", ...baseHeaders };
861
- const response = await fetch(url, {
862
- method: "POST",
863
- headers,
864
- body: JSON.stringify({ input: content }),
865
- signal
866
- });
867
- if (!response.ok) {
868
- const errorText = await response.text();
869
- throw new Error(`HTTP ${response.status}: ${errorText}`);
870
- }
871
- const text = await response.text();
872
- try {
873
- const parsed = JSON.parse(text);
874
- if (typeof parsed === "string") return parsed;
875
- if (typeof parsed.output === "string") return parsed.output;
876
- if (typeof parsed.result === "string") return parsed.result;
877
- } catch {
878
- }
879
- return text;
880
- }
881
-
882
- // src/hooks/useStreamManager.ts
883
- function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
884
- const abortControllerRef = react.useRef(null);
885
- const configRef = react.useRef(config);
886
- configRef.current = config;
887
- const callbacksRef = react.useRef(callbacks);
888
- callbacksRef.current = callbacks;
889
- const startStream = react.useCallback(
890
- async (userMessage, streamingId, sessionId, externalAbortController) => {
891
- abortControllerRef.current?.abort();
892
- const abortController = externalAbortController ?? new AbortController();
893
- abortControllerRef.current = abortController;
894
- const state = {
895
- accumulatedContent: "",
896
- executionId: void 0,
897
- currentSessionId: void 0,
898
- finalData: void 0,
899
- steps: [],
900
- stepCounter: 0,
901
- currentExecutingStepId: void 0,
902
- currentThinkingStepId: void 0,
903
- hasError: false,
904
- errorMessage: "",
905
- userActionRequest: void 0,
906
- userActionPending: false,
907
- userActionResult: void 0,
908
- allThinkingText: "",
909
- inOrchestratorPhase: false,
910
- inAggregatorPhase: false
911
- };
912
- const THROTTLE_MS = 120;
913
- const CHARS_PER_TICK = 10;
914
- const displayedLengthRef = { current: 0 };
915
- let throttleIntervalId = null;
916
- const getActiveStepMessage = () => state.steps.find((s) => s.id === state.currentExecutingStepId)?.message || [...state.steps].reverse().find((s) => s.status === "in_progress")?.message;
917
- const advanceDisplayLength = (full) => {
918
- let newLen = Math.min(displayedLengthRef.current + CHARS_PER_TICK, full.length);
919
- if (newLen > 0 && newLen < full.length) {
920
- const code = full.charCodeAt(newLen - 1);
921
- if (code >= 55296 && code <= 56319) {
922
- newLen = Math.min(newLen + 1, full.length);
923
- }
924
- }
925
- displayedLengthRef.current = newLen;
926
- };
927
- const clearThrottle = () => {
928
- if (throttleIntervalId != null) {
929
- clearInterval(throttleIntervalId);
930
- throttleIntervalId = null;
931
- }
932
- };
933
- abortController.signal.addEventListener("abort", clearThrottle, { once: true });
934
- const ensureThrottle = () => {
935
- if (throttleIntervalId != null) return;
936
- throttleIntervalId = setInterval(() => {
937
- if (abortController.signal.aborted) {
938
- clearThrottle();
939
- return;
940
- }
941
- const full = state.activeThinkingText ?? "";
942
- if (displayedLengthRef.current < full.length) {
943
- advanceDisplayLength(full);
944
- const displayText = full.slice(0, displayedLengthRef.current);
945
- setMessages(
946
- (prev) => prev.map(
947
- (msg) => msg.id === streamingId ? {
948
- ...msg,
949
- ...createStreamingMessageUpdate({
950
- ...state,
951
- activeThinkingText: displayText,
952
- currentMessage: getActiveStepMessage() || "Thinking..."
953
- })
954
- } : msg
955
- )
956
- );
957
- }
958
- }, THROTTLE_MS);
959
- };
960
- const currentConfig = configRef.current;
961
- const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
962
- const url = buildStreamingUrl(currentConfig);
963
- const headers = buildRequestHeaders(currentConfig);
964
- try {
965
- await streamWorkflowEvents(url, requestBody, headers, {
966
- signal: abortController.signal,
967
- onEvent: (event) => {
968
- if (abortController.signal.aborted) {
969
- return;
970
- }
971
- if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
972
- if (event.executionId) state.executionId = event.executionId;
973
- if (event.sessionId) state.currentSessionId = event.sessionId;
974
- return;
975
- }
976
- if (event.executionId) state.executionId = event.executionId;
977
- if (event.sessionId) state.currentSessionId = event.sessionId;
978
- const activeThinkingLengthBeforeProcess = state.activeThinkingText?.length ?? 0;
979
- processStreamEvent(event, state);
980
- const eventType = event.eventType;
981
- if (eventType === "INTENT_THINKING") {
982
- displayedLengthRef.current = 0;
983
- ensureThrottle();
984
- } else if (eventType !== "INTENT_THINKING_CONT") {
985
- displayedLengthRef.current = activeThinkingLengthBeforeProcess;
986
- clearThrottle();
987
- }
988
- if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
989
- callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
990
- } else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
991
- const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
992
- callbacksRef.current.onUserActionEvent?.(eventType, msg);
993
- }
994
- const isIntentThinkingEvent = eventType === "INTENT_THINKING" || eventType === "INTENT_THINKING_CONT";
995
- const rawMessage = event.message?.trim() || event.errorMessage?.trim();
996
- const currentMessage = isIntentThinkingEvent ? getActiveStepMessage() || "Thinking..." : rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : getActiveStepMessage() || getEventMessage(event));
997
- const displayThinking = state.activeThinkingText != null ? state.activeThinkingText.slice(0, displayedLengthRef.current) : state.activeThinkingText;
998
- setMessages(
999
- (prev) => prev.map(
1000
- (msg) => msg.id === streamingId ? {
1001
- ...msg,
1002
- ...createStreamingMessageUpdate({
1003
- ...state,
1004
- activeThinkingText: displayThinking,
1005
- currentMessage
1006
- })
1007
- } : msg
1008
- )
1009
- );
1010
- },
1011
- onError: (error) => {
1012
- clearThrottle();
1013
- setIsWaitingForResponse(false);
1014
- if (error.name !== "AbortError") {
1015
- callbacksRef.current.onError?.(error);
1016
- }
1017
- if (state.userActionPending) {
1018
- state.userActionPending = false;
1019
- state.userActionRequest = void 0;
1020
- callbacksRef.current.onUserActionEvent?.(
1021
- "USER_ACTION_FAILED",
1022
- "Connection lost. Please try again."
1023
- );
1024
- }
1025
- setMessages(
1026
- (prev) => prev.map(
1027
- (msg) => msg.id === streamingId ? {
1028
- ...msg,
1029
- ...createErrorMessageUpdate(error, state)
1030
- } : msg
1031
- )
1032
- );
1033
- },
1034
- onComplete: () => {
1035
- clearThrottle();
1036
- setIsWaitingForResponse(false);
1037
- if (state.userActionPending) {
1038
- state.userActionPending = false;
1039
- state.userActionRequest = void 0;
1040
- callbacksRef.current.onUserActionEvent?.(
1041
- "USER_ACTION_FAILED",
1042
- "Verification could not be completed."
1043
- );
1044
- }
1045
- if (state.currentSessionId && state.currentSessionId !== sessionId) {
1046
- callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
1047
- }
1048
- const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.accumulatedContent);
1049
- const finalMessage = createFinalMessage(streamingId, {
1050
- ...state,
1051
- sessionId: state.currentSessionId || sessionId,
1052
- isResolvingImages: needsImageResolve
1053
- });
1054
- setMessages(
1055
- (prev) => prev.map(
1056
- (msg) => msg.id === streamingId ? finalMessage : msg
1057
- )
1058
- );
1059
- callbacksRef.current.onStreamComplete?.(finalMessage);
1060
- }
1061
- });
1062
- clearThrottle();
1063
- const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.accumulatedContent);
1064
- if (shouldResolveImages) {
1065
- await waitForNextPaint(abortController.signal);
1066
- }
1067
- if (shouldResolveImages && !abortController.signal.aborted) {
1068
- try {
1069
- const resolvedContent = await resolveRagImageUrls(
1070
- currentConfig,
1071
- state.accumulatedContent,
1072
- abortController.signal
1073
- );
1074
- setMessages(
1075
- (prev) => prev.map(
1076
- (msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
1077
- )
1078
- );
1079
- } catch {
1080
- setMessages(
1081
- (prev) => prev.map(
1082
- (msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
1083
- )
1084
- );
1085
- }
1086
- }
1087
- return state.currentSessionId;
1088
- } catch (error) {
1089
- clearThrottle();
1090
- setIsWaitingForResponse(false);
1091
- if (error.name !== "AbortError") {
1092
- callbacksRef.current.onError?.(error);
1093
- }
1094
- if (state.userActionPending) {
1095
- state.userActionPending = false;
1096
- state.userActionRequest = void 0;
1097
- callbacksRef.current.onUserActionEvent?.(
1098
- "USER_ACTION_FAILED",
1099
- "Connection lost. Please try again."
1100
- );
1101
- }
1102
- setMessages(
1103
- (prev) => prev.map(
1104
- (msg) => msg.id === streamingId ? {
1105
- ...msg,
1106
- ...createErrorMessageUpdate(error, state)
1107
- } : msg
1108
- )
1109
- );
1110
- return state.currentSessionId;
1111
- }
1112
- },
1113
- [setMessages, setIsWaitingForResponse]
1114
- );
1115
- const cancelStream = react.useCallback(() => {
1116
- abortControllerRef.current?.abort();
1117
- }, []);
1118
- return {
1119
- startStream,
1120
- cancelStream,
1121
- abortControllerRef
1122
- };
1123
- }
1124
-
1125
- // src/hooks/useChat.ts
1126
- function useChat(config, callbacks = {}) {
1127
- const [messages, setMessages] = react.useState(() => {
1128
- if (config.userId) return chatStore.get(config.userId);
1129
- return config.initialMessages ?? [];
1130
- });
1131
- const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1132
- const sessionIdRef = react.useRef(
1133
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
1134
- );
1135
- const prevUserIdRef = react.useRef(config.userId);
1136
- const callbacksRef = react.useRef(callbacks);
1137
- callbacksRef.current = callbacks;
1138
- const configRef = react.useRef(config);
1139
- configRef.current = config;
1140
- const messagesRef = react.useRef(messages);
1141
- messagesRef.current = messages;
1142
- const storeAwareSetMessages = react.useCallback(
1143
- (updater) => {
1144
- const { userId } = configRef.current;
1145
- if (userId && activeStreamStore.has(userId)) {
1146
- activeStreamStore.applyMessages(userId, updater);
1147
- }
1148
- setMessages(updater);
1149
- },
1150
- // eslint-disable-next-line react-hooks/exhaustive-deps
1151
- []
1152
- );
1153
- const storeAwareSetIsWaiting = react.useCallback(
1154
- (waiting) => {
1155
- const { userId } = configRef.current;
1156
- if (userId && activeStreamStore.has(userId)) {
1157
- activeStreamStore.setWaiting(userId, waiting);
1158
- }
1159
- setIsWaitingForResponse(waiting);
1160
- },
1161
- // eslint-disable-next-line react-hooks/exhaustive-deps
1162
- []
1163
- );
1164
- const [userActionState, setUserActionState] = react.useState({
1165
- request: null,
1166
- result: null,
1167
- clearOtpTrigger: 0
1168
- });
1169
- const userActionStateRef = react.useRef(userActionState);
1170
- userActionStateRef.current = userActionState;
1171
- const wrappedCallbacks = react.useMemo(() => ({
1172
- ...callbacksRef.current,
1173
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1174
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1175
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1176
- onError: (error) => callbacksRef.current.onError?.(error),
1177
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1178
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1179
- onUserActionRequired: (request) => {
1180
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1181
- callbacksRef.current.onUserActionRequired?.(request);
1182
- },
1183
- onUserActionEvent: (eventType, message) => {
1184
- switch (eventType) {
1185
- case "USER_ACTION_SUCCESS":
1186
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1187
- break;
1188
- case "USER_ACTION_REJECTED":
1189
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1190
- break;
1191
- case "USER_ACTION_EXPIRED":
1192
- case "USER_ACTION_FAILED":
1193
- setUserActionState((prev) => ({ ...prev, request: null }));
1194
- break;
1195
- case "USER_ACTION_INVALID":
1196
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1197
- break;
1198
- }
1199
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1200
- }
1201
- // eslint-disable-next-line react-hooks/exhaustive-deps
1202
- }), []);
1203
- const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
1204
- config,
1205
- wrappedCallbacks,
1206
- storeAwareSetMessages,
1207
- storeAwareSetIsWaiting
1208
- );
1209
- const sendMessage = react.useCallback(
1210
- async (userMessage) => {
1211
- if (!userMessage.trim()) return;
1212
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1213
- sessionIdRef.current = generateId();
1214
- callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1215
- }
1216
- const userMessageId = `user-${Date.now()}`;
1217
- const userMsg = {
1218
- id: userMessageId,
1219
- sessionId: sessionIdRef.current,
1220
- role: "user",
1221
- content: userMessage,
1222
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1223
- };
1224
- setMessages((prev) => [...prev, userMsg]);
1225
- callbacksRef.current.onMessageSent?.(userMessage);
1226
- setIsWaitingForResponse(true);
1227
- callbacksRef.current.onStreamStart?.();
1228
- const streamingId = `assistant-${Date.now()}`;
1229
- const streamingMsg = {
1230
- id: streamingId,
1231
- sessionId: sessionIdRef.current,
1232
- role: "assistant",
1233
- content: "",
1234
- streamingContent: "",
1235
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1236
- isStreaming: true,
1237
- streamProgress: "started",
1238
- steps: [],
1239
- currentExecutingStepId: void 0,
1240
- isCancelled: false,
1241
- currentMessage: void 0
1242
- };
1243
- setMessages((prev) => [...prev, streamingMsg]);
1244
- const abortController = new AbortController();
1245
- const { userId } = configRef.current;
1246
- if (userId) {
1247
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1248
- activeStreamStore.start(userId, abortController, initialMessages);
1249
- }
1250
- const newSessionId = await startStream(
1251
- userMessage,
1252
- streamingId,
1253
- sessionIdRef.current,
1254
- abortController
1255
- );
1256
- if (userId) {
1257
- activeStreamStore.complete(userId);
1258
- }
1259
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1260
- sessionIdRef.current = newSessionId;
1261
- }
1262
- },
1263
- [startStream]
1264
- );
1265
- const clearMessages = react.useCallback(() => {
1266
- if (configRef.current.userId) {
1267
- chatStore.delete(configRef.current.userId);
1268
- }
1269
- setMessages([]);
1270
- }, []);
1271
- const prependMessages = react.useCallback((msgs) => {
1272
- setMessages((prev) => [...msgs, ...prev]);
1273
- }, []);
1274
- const cancelStream = react.useCallback(() => {
1275
- if (configRef.current.userId) {
1276
- activeStreamStore.abort(configRef.current.userId);
1277
- }
1278
- cancelStreamManager();
1279
- setIsWaitingForResponse(false);
1280
- setUserActionState((prev) => ({ ...prev, request: null, result: null }));
1281
- setMessages(
1282
- (prev) => prev.map((msg) => {
1283
- if (msg.isStreaming) {
1284
- return {
1285
- ...msg,
1286
- ...createCancelledMessageUpdate(
1287
- msg.steps || [],
1288
- msg.currentMessage
1289
- )
1290
- };
1291
- }
1292
- return msg;
1293
- })
1294
- );
1295
- }, [cancelStreamManager]);
1296
- const resetSession = react.useCallback(() => {
1297
- if (configRef.current.userId) {
1298
- activeStreamStore.abort(configRef.current.userId);
1299
- chatStore.delete(configRef.current.userId);
1300
- }
1301
- setMessages([]);
1302
- sessionIdRef.current = void 0;
1303
- abortControllerRef.current?.abort();
1304
- setIsWaitingForResponse(false);
1305
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1306
- }, []);
1307
- const getSessionId = react.useCallback(() => {
1308
- return sessionIdRef.current;
1309
- }, []);
1310
- const getMessages = react.useCallback(() => {
1311
- return messages;
1312
- }, [messages]);
1313
- const approveUserAction = react.useCallback(
1314
- async (otp) => {
1315
- const request = userActionStateRef.current.request;
1316
- if (!request) return;
1317
- try {
1318
- await submitUserAction(configRef.current, request.userActionId, { otp });
1319
- } catch (error) {
1320
- setUserActionState((prev) => ({
1321
- ...prev,
1322
- clearOtpTrigger: prev.clearOtpTrigger + 1
1323
- }));
1324
- callbacksRef.current.onError?.(error);
1325
- throw error;
1326
- }
1327
- },
1328
- []
1329
- );
1330
- const rejectUserAction = react.useCallback(async () => {
1331
- const request = userActionStateRef.current.request;
1332
- if (!request) return;
1333
- try {
1334
- setMessages((prev) => {
1335
- let lastStreamingIdx = -1;
1336
- for (let i = prev.length - 1; i >= 0; i--) {
1337
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
1338
- lastStreamingIdx = i;
1339
- break;
1340
- }
1341
- }
1342
- if (lastStreamingIdx === -1) return prev;
1343
- return prev.map(
1344
- (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
1345
- );
1346
- });
1347
- await cancelUserAction(configRef.current, request.userActionId);
1348
- } catch (error) {
1349
- callbacksRef.current.onError?.(error);
1350
- throw error;
1351
- }
1352
- }, []);
1353
- const resendOtp = react.useCallback(async () => {
1354
- const request = userActionStateRef.current.request;
1355
- if (!request) return;
1356
- try {
1357
- await resendUserAction(configRef.current, request.userActionId);
1358
- } catch (error) {
1359
- callbacksRef.current.onError?.(error);
1360
- throw error;
1361
- }
1362
- }, []);
1363
- react.useEffect(() => {
1364
- const { userId } = config;
1365
- if (!userId) return;
1366
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1367
- setMessages(msgs);
1368
- setIsWaitingForResponse(isWaiting);
1369
- });
1370
- const active = activeStreamStore.get(userId);
1371
- if (active) {
1372
- setMessages(active.messages);
1373
- setIsWaitingForResponse(active.isWaiting);
1374
- }
1375
- return unsubscribe;
1376
- }, []);
1377
- react.useEffect(() => {
1378
- if (!config.userId) return;
1379
- const toSave = messages.filter((m) => !m.isStreaming);
1380
- if (toSave.length > 0) {
1381
- chatStore.set(config.userId, toSave);
1382
- }
1383
- }, [messages, config.userId]);
1384
- react.useEffect(() => {
1385
- const prevUserId = prevUserIdRef.current;
1386
- prevUserIdRef.current = config.userId;
1387
- if (prevUserId === config.userId) return;
1388
- if (prevUserId && !config.userId) {
1389
- chatStore.delete(prevUserId);
1390
- setMessages([]);
1391
- sessionIdRef.current = void 0;
1392
- setIsWaitingForResponse(false);
1393
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1394
- } else if (config.userId) {
1395
- const stored = chatStore.get(config.userId);
1396
- setMessages(stored);
1397
- sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
1398
- }
1399
- }, [config.userId]);
1400
- return {
1401
- messages,
1402
- sendMessage,
1403
- clearMessages,
1404
- prependMessages,
1405
- cancelStream,
1406
- resetSession,
1407
- getSessionId,
1408
- getMessages,
1409
- isWaitingForResponse,
1410
- sessionId: sessionIdRef.current,
1411
- // User action (OTP) state and methods
1412
- userActionState,
1413
- approveUserAction,
1414
- rejectUserAction,
1415
- resendOtp
1416
- };
1417
- }
435
+ async function submitUserAction(config, userActionId, data) {
436
+ return sendUserActionRequest(config, userActionId, "submit", data);
437
+ }
438
+ async function cancelUserAction(config, userActionId) {
439
+ return sendUserActionRequest(config, userActionId, "cancel");
440
+ }
441
+ async function resendUserAction(config, userActionId) {
442
+ return sendUserActionRequest(config, userActionId, "resend");
443
+ }
444
+ var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
445
+ var chatStore = {
446
+ get(key) {
447
+ const raw = storage.getString(key);
448
+ if (!raw) return [];
449
+ try {
450
+ return JSON.parse(raw);
451
+ } catch {
452
+ return [];
453
+ }
454
+ },
455
+ set(key, messages) {
456
+ storage.set(key, JSON.stringify(messages));
457
+ },
458
+ delete(key) {
459
+ storage.delete(key);
460
+ }
461
+ };
462
+
463
+ // src/utils/activeStreamStore.ts
464
+ var streams = /* @__PURE__ */ new Map();
465
+ var activeStreamStore = {
466
+ has(key) {
467
+ return streams.has(key);
468
+ },
469
+ get(key) {
470
+ const entry = streams.get(key);
471
+ if (!entry) return null;
472
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
473
+ },
474
+ // Called before startStream — registers the controller and initial messages
475
+ start(key, abortController, initialMessages) {
476
+ const existing = streams.get(key);
477
+ streams.set(key, {
478
+ messages: initialMessages,
479
+ isWaiting: true,
480
+ abortController,
481
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
482
+ });
483
+ },
484
+ // Called by the stream on every event — applies the same updater pattern React uses
485
+ applyMessages(key, updater) {
486
+ const entry = streams.get(key);
487
+ if (!entry) return;
488
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
489
+ entry.messages = next;
490
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
491
+ },
492
+ setWaiting(key, waiting) {
493
+ const entry = streams.get(key);
494
+ if (!entry) return;
495
+ entry.isWaiting = waiting;
496
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
497
+ },
498
+ // Called when stream completes — persists to chatStore and cleans up
499
+ complete(key) {
500
+ const entry = streams.get(key);
501
+ if (!entry) return;
502
+ entry.isWaiting = false;
503
+ entry.listeners.forEach((l) => l(entry.messages, false));
504
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
505
+ if (toSave.length > 0) chatStore.set(key, toSave);
506
+ streams.delete(key);
507
+ },
508
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
509
+ subscribe(key, listener) {
510
+ const entry = streams.get(key);
511
+ if (!entry) return () => {
512
+ };
513
+ entry.listeners.add(listener);
514
+ return () => {
515
+ streams.get(key)?.listeners.delete(listener);
516
+ };
517
+ },
518
+ // Explicit user cancel — aborts the controller and removes the entry
519
+ abort(key) {
520
+ const entry = streams.get(key);
521
+ if (!entry) return;
522
+ entry.abortController.abort();
523
+ streams.delete(key);
524
+ }
525
+ };
1418
526
 
1419
527
  // src/utils/v2EventProcessor.ts
1420
528
  function getEventText(event, field) {
@@ -1440,7 +548,7 @@ function addThinkingLine(state, header, detail) {
1440
548
  function appendThinkingText(state, text) {
1441
549
  state.formattedThinkingText += text;
1442
550
  }
1443
- function completeLastInProgressStep2(steps) {
551
+ function completeLastInProgressStep(steps) {
1444
552
  for (let i = steps.length - 1; i >= 0; i--) {
1445
553
  if (steps[i].status === "in_progress") {
1446
554
  steps[i].status = "completed";
@@ -1654,7 +762,7 @@ function processStreamEventV2(event, state) {
1654
762
  break;
1655
763
  }
1656
764
  case "USER_ACTION_REQUIRED": {
1657
- completeLastInProgressStep2(state.steps);
765
+ completeLastInProgressStep(state.steps);
1658
766
  if (event.userActionRequest) {
1659
767
  state.userActionRequest = {
1660
768
  userActionId: event.userActionRequest.userActionId,
@@ -1666,14 +774,18 @@ function processStreamEventV2(event, state) {
1666
774
  }
1667
775
  state.userActionPending = true;
1668
776
  const req = event.userActionRequest;
777
+ const displayMessage = getUserActionDisplayMessage(
778
+ eventType,
779
+ req?.message || message
780
+ );
1669
781
  if (req) {
1670
- addThinkingLine(state, "**Verification Required**", req.message || "Waiting for authorization...");
782
+ addThinkingLine(state, "**Verification Required**", displayMessage);
1671
783
  }
1672
784
  const stepId = `step-${state.stepCounter++}`;
1673
785
  state.steps.push({
1674
786
  id: stepId,
1675
787
  eventType,
1676
- message,
788
+ message: displayMessage,
1677
789
  status: "in_progress",
1678
790
  timestamp: Date.now(),
1679
791
  elapsedMs: event.elapsedMs
@@ -1683,8 +795,9 @@ function processStreamEventV2(event, state) {
1683
795
  break;
1684
796
  }
1685
797
  case "USER_ACTION_SUCCESS": {
1686
- appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
1687
- completeLastInProgressStep2(state.steps);
798
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
799
+ appendThinkingText(state, "\n\u2713 " + displayMessage);
800
+ completeLastInProgressStep(state.steps);
1688
801
  state.userActionRequest = void 0;
1689
802
  state.userActionPending = false;
1690
803
  state.userActionResult = "approved";
@@ -1692,7 +805,7 @@ function processStreamEventV2(event, state) {
1692
805
  state.steps.push({
1693
806
  id: stepId,
1694
807
  eventType,
1695
- message,
808
+ message: displayMessage,
1696
809
  status: "completed",
1697
810
  timestamp: Date.now(),
1698
811
  elapsedMs: event.elapsedMs
@@ -1701,12 +814,14 @@ function processStreamEventV2(event, state) {
1701
814
  break;
1702
815
  }
1703
816
  case "USER_ACTION_INVALID": {
1704
- completeLastInProgressStep2(state.steps);
817
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
818
+ appendThinkingText(state, "\n\u2717 " + displayMessage);
819
+ completeLastInProgressStep(state.steps);
1705
820
  const errorStepId = `step-${state.stepCounter++}`;
1706
821
  state.steps.push({
1707
822
  id: errorStepId,
1708
823
  eventType,
1709
- message,
824
+ message: displayMessage,
1710
825
  status: "error",
1711
826
  timestamp: Date.now(),
1712
827
  elapsedMs: event.elapsedMs
@@ -1724,8 +839,9 @@ function processStreamEventV2(event, state) {
1724
839
  break;
1725
840
  }
1726
841
  case "USER_ACTION_REJECTED": {
1727
- appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
1728
- completeLastInProgressStep2(state.steps);
842
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
843
+ appendThinkingText(state, "\n" + displayMessage);
844
+ completeLastInProgressStep(state.steps);
1729
845
  state.userActionRequest = void 0;
1730
846
  state.userActionPending = false;
1731
847
  state.userActionResult = "rejected";
@@ -1733,7 +849,7 @@ function processStreamEventV2(event, state) {
1733
849
  state.steps.push({
1734
850
  id: stepId,
1735
851
  eventType,
1736
- message,
852
+ message: displayMessage,
1737
853
  status: "completed",
1738
854
  timestamp: Date.now(),
1739
855
  elapsedMs: event.elapsedMs
@@ -1742,15 +858,16 @@ function processStreamEventV2(event, state) {
1742
858
  break;
1743
859
  }
1744
860
  case "USER_ACTION_EXPIRED": {
1745
- appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
1746
- completeLastInProgressStep2(state.steps);
861
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
862
+ appendThinkingText(state, "\n\u2717 " + displayMessage);
863
+ completeLastInProgressStep(state.steps);
1747
864
  state.userActionRequest = void 0;
1748
865
  state.userActionPending = false;
1749
866
  const stepId = `step-${state.stepCounter++}`;
1750
867
  state.steps.push({
1751
868
  id: stepId,
1752
869
  eventType,
1753
- message,
870
+ message: displayMessage,
1754
871
  status: "error",
1755
872
  timestamp: Date.now(),
1756
873
  elapsedMs: event.elapsedMs
@@ -1759,11 +876,13 @@ function processStreamEventV2(event, state) {
1759
876
  break;
1760
877
  }
1761
878
  case "USER_ACTION_RESENT": {
879
+ const displayMessage = getUserActionDisplayMessage(eventType, event.message);
880
+ appendThinkingText(state, "\n" + displayMessage);
1762
881
  const stepId = `step-${state.stepCounter++}`;
1763
882
  state.steps.push({
1764
883
  id: stepId,
1765
884
  eventType,
1766
- message,
885
+ message: displayMessage,
1767
886
  status: "completed",
1768
887
  timestamp: Date.now(),
1769
888
  elapsedMs: event.elapsedMs
@@ -1772,15 +891,19 @@ function processStreamEventV2(event, state) {
1772
891
  break;
1773
892
  }
1774
893
  case "USER_ACTION_FAILED": {
1775
- appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
1776
- completeLastInProgressStep2(state.steps);
894
+ const displayMessage = getUserActionDisplayMessage(
895
+ eventType,
896
+ event.message || event.errorMessage
897
+ );
898
+ appendThinkingText(state, "\n\u2717 " + displayMessage);
899
+ completeLastInProgressStep(state.steps);
1777
900
  state.userActionRequest = void 0;
1778
901
  state.userActionPending = false;
1779
902
  const stepId = `step-${state.stepCounter++}`;
1780
903
  state.steps.push({
1781
904
  id: stepId,
1782
905
  eventType,
1783
- message,
906
+ message: displayMessage,
1784
907
  status: "error",
1785
908
  timestamp: Date.now(),
1786
909
  elapsedMs: event.elapsedMs
@@ -1811,8 +934,58 @@ function processStreamEventV2(event, state) {
1811
934
  return state;
1812
935
  }
1813
936
 
937
+ // src/utils/ragImageResolver.ts
938
+ var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
939
+ function hasRagImages(content) {
940
+ return RAG_IMAGE_REGEX.test(content);
941
+ }
942
+ async function waitForNextPaint(signal) {
943
+ if (signal?.aborted) return;
944
+ await new Promise((resolve) => {
945
+ let isSettled = false;
946
+ const finish = () => {
947
+ if (isSettled) return;
948
+ isSettled = true;
949
+ signal?.removeEventListener("abort", finish);
950
+ resolve();
951
+ };
952
+ signal?.addEventListener("abort", finish, { once: true });
953
+ if (typeof requestAnimationFrame === "function") {
954
+ requestAnimationFrame(() => {
955
+ setTimeout(finish, 0);
956
+ });
957
+ return;
958
+ }
959
+ setTimeout(finish, 0);
960
+ });
961
+ }
962
+ async function resolveRagImageUrls(config, content, signal) {
963
+ const url = buildResolveImagesUrl(config);
964
+ const baseHeaders = buildRequestHeaders(config);
965
+ const headers = { "Content-Type": "application/json", ...baseHeaders };
966
+ const response = await fetch(url, {
967
+ method: "POST",
968
+ headers,
969
+ body: JSON.stringify({ input: content }),
970
+ signal
971
+ });
972
+ if (!response.ok) {
973
+ const errorText = await response.text();
974
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
975
+ }
976
+ const text = await response.text();
977
+ try {
978
+ const parsed = JSON.parse(text);
979
+ if (typeof parsed === "string") return parsed;
980
+ if (typeof parsed.output === "string") return parsed.output;
981
+ if (typeof parsed.result === "string") return parsed.result;
982
+ } catch {
983
+ }
984
+ return text;
985
+ }
986
+
1814
987
  // src/hooks/useStreamManagerV2.ts
1815
- var FRIENDLY_ERROR_MESSAGE2 = "Oops, something went wrong. Please try again.";
988
+ var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
1816
989
  function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
1817
990
  const abortControllerRef = react.useRef(null);
1818
991
  const configRef = react.useRef(config);
@@ -1852,7 +1025,10 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1852
1025
  if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
1853
1026
  callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
1854
1027
  } else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
1855
- const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
1028
+ const msg = getUserActionDisplayMessage(
1029
+ eventType,
1030
+ event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
1031
+ );
1856
1032
  callbacksRef.current.onUserActionEvent?.(eventType, msg);
1857
1033
  }
1858
1034
  const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
@@ -1860,8 +1036,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1860
1036
  const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
1861
1037
  if (state.hasError) {
1862
1038
  updateMessage({
1863
- streamingContent: FRIENDLY_ERROR_MESSAGE2,
1864
- content: FRIENDLY_ERROR_MESSAGE2,
1039
+ streamingContent: FRIENDLY_ERROR_MESSAGE,
1040
+ content: FRIENDLY_ERROR_MESSAGE,
1865
1041
  streamProgress: "error",
1866
1042
  isError: true,
1867
1043
  errorDetails: state.errorMessage,
@@ -1911,7 +1087,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1911
1087
  isError: !isAborted,
1912
1088
  isCancelled: isAborted,
1913
1089
  errorDetails: isAborted ? void 0 : error.message,
1914
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1090
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1915
1091
  currentMessage: isAborted ? "Thinking..." : void 0,
1916
1092
  formattedThinkingText: state.formattedThinkingText || void 0,
1917
1093
  steps: [...state.steps].map((step) => {
@@ -1943,7 +1119,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1943
1119
  id: streamingId,
1944
1120
  sessionId: state.sessionId || sessionId,
1945
1121
  role: "assistant",
1946
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE2 : state.finalResponse || "",
1122
+ content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
1947
1123
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1948
1124
  isStreaming: false,
1949
1125
  streamProgress: state.hasError ? "error" : "completed",
@@ -2014,7 +1190,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
2014
1190
  isError: !isAborted,
2015
1191
  isCancelled: isAborted,
2016
1192
  errorDetails: isAborted ? void 0 : error.message,
2017
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1193
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
2018
1194
  formattedThinkingText: state.formattedThinkingText || void 0,
2019
1195
  steps: [...state.steps].map((step) => {
2020
1196
  if (step.status === "in_progress" && isAborted) {
@@ -2041,45 +1217,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
2041
1217
  };
2042
1218
  }
2043
1219
 
1220
+ // src/utils/workflowUsersClient.ts
1221
+ var DEFAULT_SESSION_PAGE_SIZE = 20;
1222
+ var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
1223
+ function getStageParamName2(config) {
1224
+ return config.api.stageQueryParam ?? "stage";
1225
+ }
1226
+ function requireOwnerId(config) {
1227
+ const ownerId = config.session?.owner?.id;
1228
+ if (!ownerId) {
1229
+ throw new Error(
1230
+ "workflowUsersClient: session.owner.id is required to call this endpoint."
1231
+ );
1232
+ }
1233
+ return ownerId;
1234
+ }
1235
+ function requireWorkflowName(config) {
1236
+ const workflowName = config.workflow.name;
1237
+ if (!workflowName) {
1238
+ throw new Error(
1239
+ "workflowUsersClient: workflow.name is required to call this endpoint."
1240
+ );
1241
+ }
1242
+ return workflowName;
1243
+ }
1244
+ function requireWorkflowId(config) {
1245
+ const workflowId = config.workflow.id;
1246
+ if (!workflowId) {
1247
+ throw new Error(
1248
+ "workflowUsersClient: workflow.id is required to call this endpoint."
1249
+ );
1250
+ }
1251
+ return workflowId;
1252
+ }
1253
+ function isPlaygroundYaakAuth(config) {
1254
+ if (config.api.authToken) return false;
1255
+ const headers = config.api.headers;
1256
+ if (!headers) return false;
1257
+ for (const key of Object.keys(headers)) {
1258
+ const lower = key.toLowerCase();
1259
+ if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
1260
+ }
1261
+ return false;
1262
+ }
1263
+ async function fetchJson(url, headers, signal) {
1264
+ const response = await fetch(url, {
1265
+ method: "GET",
1266
+ headers,
1267
+ signal
1268
+ });
1269
+ if (!response.ok) {
1270
+ const errorText = await response.text();
1271
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1272
+ }
1273
+ return await response.json();
1274
+ }
1275
+ async function listSessions(config, opts = {}) {
1276
+ const ownerId = requireOwnerId(config);
1277
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1278
+ const params = new URLSearchParams({
1279
+ page: String(opts.page ?? 0),
1280
+ size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
1281
+ });
1282
+ if (useYaakEndpoint) {
1283
+ params.set("workflowUserId", ownerId);
1284
+ params.set("workflowName", requireWorkflowName(config));
1285
+ } else {
1286
+ params.set("workflowId", requireWorkflowId(config));
1287
+ }
1288
+ if (config.workflow.stage) {
1289
+ params.set(getStageParamName2(config), config.workflow.stage);
1290
+ }
1291
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1292
+ ownerId
1293
+ )}/sessions?${params.toString()}`;
1294
+ return fetchJson(
1295
+ url,
1296
+ buildRequestHeaders(config),
1297
+ opts.signal
1298
+ );
1299
+ }
1300
+ async function listConversations(config, opts) {
1301
+ const ownerId = requireOwnerId(config);
1302
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1303
+ const params = new URLSearchParams({
1304
+ sessionId: opts.sessionId,
1305
+ page: String(opts.page ?? 0),
1306
+ size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
1307
+ });
1308
+ if (useYaakEndpoint) {
1309
+ params.set("workflowUserId", ownerId);
1310
+ params.set("workflowName", requireWorkflowName(config));
1311
+ }
1312
+ if (config.workflow.stage) {
1313
+ params.set(getStageParamName2(config), config.workflow.stage);
1314
+ }
1315
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1316
+ ownerId
1317
+ )}/conversations?${params.toString()}`;
1318
+ return fetchJson(
1319
+ url,
1320
+ buildRequestHeaders(config),
1321
+ opts.signal
1322
+ );
1323
+ }
1324
+
2044
1325
  // src/hooks/useChatV2.ts
1326
+ function conversationEntryToMessages(entry, sessionId) {
1327
+ return [
1328
+ {
1329
+ id: `history-user-${entry.executionId}`,
1330
+ sessionId,
1331
+ role: "user",
1332
+ content: entry.query,
1333
+ timestamp: entry.createdAt,
1334
+ isHistorical: true
1335
+ },
1336
+ {
1337
+ id: `history-assistant-${entry.executionId}`,
1338
+ sessionId,
1339
+ role: "assistant",
1340
+ content: entry.response,
1341
+ timestamp: entry.createdAt,
1342
+ executionId: entry.executionId,
1343
+ isHistorical: true
1344
+ }
1345
+ ];
1346
+ }
1347
+ function streamKeyFor(scopeKey, sessionId) {
1348
+ return `${scopeKey}|sid:${sessionId ?? ""}`;
1349
+ }
2045
1350
  function useChatV2(config, callbacks = {}) {
1351
+ const scopeKey = react.useMemo(() => buildScopeKey(config), [
1352
+ config.session?.userId,
1353
+ config.workflow.id,
1354
+ config.workflow.version,
1355
+ config.workflow.stage
1356
+ ]);
1357
+ const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
2046
1358
  const [messages, setMessages] = react.useState(() => {
2047
- if (config.userId) return chatStore.get(config.userId);
2048
- return config.initialMessages ?? [];
1359
+ const stored = chatStore.get(scopeKey);
1360
+ if (stored.length > 0) return stored;
1361
+ return config.session?.initialMessages ?? [];
2049
1362
  });
2050
1363
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
2051
- const sessionIdRef = react.useRef(
2052
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
2053
- );
2054
- const prevUserIdRef = react.useRef(config.userId);
1364
+ const [loadingSessionId, setLoadingSessionId] = react.useState(void 0);
1365
+ const [currentSessionId, setCurrentSessionId] = react.useState(initialSessionId);
1366
+ const sessionIdRef = react.useRef(initialSessionId);
1367
+ const prevScopeKeyRef = react.useRef(scopeKey);
1368
+ const activeStreamSessionRef = react.useRef(void 0);
2055
1369
  const callbacksRef = react.useRef(callbacks);
2056
1370
  callbacksRef.current = callbacks;
2057
1371
  const configRef = react.useRef(config);
2058
1372
  configRef.current = config;
1373
+ const scopeKeyRef = react.useRef(scopeKey);
1374
+ scopeKeyRef.current = scopeKey;
2059
1375
  const messagesRef = react.useRef(messages);
2060
1376
  messagesRef.current = messages;
2061
1377
  const storeAwareSetMessages = react.useCallback(
2062
1378
  (updater) => {
2063
- const { userId } = configRef.current;
2064
- if (userId && activeStreamStore.has(userId)) {
2065
- activeStreamStore.applyMessages(userId, updater);
1379
+ const scope = scopeKeyRef.current;
1380
+ const streamSid = activeStreamSessionRef.current;
1381
+ if (streamSid !== void 0) {
1382
+ const streamKey = streamKeyFor(scope, streamSid);
1383
+ if (activeStreamStore.has(streamKey)) {
1384
+ activeStreamStore.applyMessages(
1385
+ streamKey,
1386
+ updater
1387
+ );
1388
+ }
1389
+ if (sessionIdRef.current === streamSid) {
1390
+ setMessages(updater);
1391
+ }
1392
+ return;
2066
1393
  }
2067
1394
  setMessages(updater);
2068
1395
  },
2069
- // eslint-disable-next-line react-hooks/exhaustive-deps
2070
1396
  []
2071
1397
  );
2072
- const storeAwareSetIsWaiting = react.useCallback(
2073
- (waiting) => {
2074
- const { userId } = configRef.current;
2075
- if (userId && activeStreamStore.has(userId)) {
2076
- activeStreamStore.setWaiting(userId, waiting);
1398
+ const storeAwareSetIsWaiting = react.useCallback((waiting) => {
1399
+ const scope = scopeKeyRef.current;
1400
+ const streamSid = activeStreamSessionRef.current;
1401
+ if (streamSid !== void 0) {
1402
+ const streamKey = streamKeyFor(scope, streamSid);
1403
+ if (activeStreamStore.has(streamKey)) {
1404
+ activeStreamStore.setWaiting(streamKey, waiting);
2077
1405
  }
2078
- setIsWaitingForResponse(waiting);
2079
- },
2080
- // eslint-disable-next-line react-hooks/exhaustive-deps
2081
- []
2082
- );
1406
+ if (sessionIdRef.current === streamSid) {
1407
+ setIsWaitingForResponse(waiting);
1408
+ }
1409
+ return;
1410
+ }
1411
+ setIsWaitingForResponse(waiting);
1412
+ }, []);
2083
1413
  const [userActionState, setUserActionState] = react.useState({
2084
1414
  request: null,
2085
1415
  result: null,
@@ -2087,38 +1417,43 @@ function useChatV2(config, callbacks = {}) {
2087
1417
  });
2088
1418
  const userActionStateRef = react.useRef(userActionState);
2089
1419
  userActionStateRef.current = userActionState;
2090
- const wrappedCallbacks = react.useMemo(() => ({
2091
- ...callbacksRef.current,
2092
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
2093
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
2094
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
2095
- onError: (error) => callbacksRef.current.onError?.(error),
2096
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
2097
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
2098
- onUserActionRequired: (request) => {
2099
- setUserActionState((prev) => ({ ...prev, request, result: null }));
2100
- callbacksRef.current.onUserActionRequired?.(request);
2101
- },
2102
- onUserActionEvent: (eventType, message) => {
2103
- switch (eventType) {
2104
- case "USER_ACTION_SUCCESS":
2105
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
2106
- break;
2107
- case "USER_ACTION_REJECTED":
2108
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
2109
- break;
2110
- case "USER_ACTION_EXPIRED":
2111
- case "USER_ACTION_FAILED":
2112
- setUserActionState((prev) => ({ ...prev, request: null }));
2113
- break;
2114
- case "USER_ACTION_INVALID":
2115
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
2116
- break;
1420
+ const wrappedCallbacks = react.useMemo(
1421
+ () => ({
1422
+ ...callbacksRef.current,
1423
+ onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1424
+ onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1425
+ onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1426
+ onError: (error) => callbacksRef.current.onError?.(error),
1427
+ onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1428
+ onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1429
+ onUserActionRequired: (request) => {
1430
+ setUserActionState((prev) => ({ ...prev, request, result: null }));
1431
+ callbacksRef.current.onUserActionRequired?.(request);
1432
+ },
1433
+ onUserActionEvent: (eventType, message) => {
1434
+ switch (eventType) {
1435
+ case "USER_ACTION_SUCCESS":
1436
+ setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1437
+ break;
1438
+ case "USER_ACTION_REJECTED":
1439
+ setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1440
+ break;
1441
+ case "USER_ACTION_EXPIRED":
1442
+ case "USER_ACTION_FAILED":
1443
+ setUserActionState((prev) => ({ ...prev, request: null }));
1444
+ break;
1445
+ case "USER_ACTION_INVALID":
1446
+ setUserActionState((prev) => ({
1447
+ ...prev,
1448
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1449
+ }));
1450
+ break;
1451
+ }
1452
+ callbacksRef.current.onUserActionEvent?.(eventType, message);
2117
1453
  }
2118
- callbacksRef.current.onUserActionEvent?.(eventType, message);
2119
- }
2120
- // eslint-disable-next-line react-hooks/exhaustive-deps
2121
- }), []);
1454
+ }),
1455
+ []
1456
+ );
2122
1457
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
2123
1458
  config,
2124
1459
  wrappedCallbacks,
@@ -2128,8 +1463,10 @@ function useChatV2(config, callbacks = {}) {
2128
1463
  const sendMessage = react.useCallback(
2129
1464
  async (userMessage) => {
2130
1465
  if (!userMessage.trim()) return;
2131
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1466
+ const autoGen = configRef.current.session?.autoGenerateId;
1467
+ if (!sessionIdRef.current && autoGen !== false) {
2132
1468
  sessionIdRef.current = generateId();
1469
+ setCurrentSessionId(sessionIdRef.current);
2133
1470
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
2134
1471
  }
2135
1472
  const userMessageId = `user-${Date.now()}`;
@@ -2161,38 +1498,40 @@ function useChatV2(config, callbacks = {}) {
2161
1498
  };
2162
1499
  setMessages((prev) => [...prev, streamingMsg]);
2163
1500
  const abortController = new AbortController();
2164
- const { userId } = configRef.current;
2165
- if (userId) {
2166
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
2167
- activeStreamStore.start(userId, abortController, initialMessages);
2168
- }
1501
+ const scope = scopeKeyRef.current;
1502
+ const streamSessionId = sessionIdRef.current;
1503
+ const streamKey = streamKeyFor(scope, streamSessionId);
1504
+ activeStreamSessionRef.current = streamSessionId;
1505
+ const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1506
+ activeStreamStore.start(streamKey, abortController, initialMessages);
2169
1507
  const newSessionId = await startStream(
2170
1508
  userMessage,
2171
1509
  streamingId,
2172
- sessionIdRef.current,
1510
+ streamSessionId,
2173
1511
  abortController
2174
1512
  );
2175
- if (userId) {
2176
- activeStreamStore.complete(userId);
2177
- }
2178
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1513
+ activeStreamStore.complete(streamKey);
1514
+ activeStreamSessionRef.current = void 0;
1515
+ if (!abortController.signal.aborted && newSessionId && newSessionId !== streamSessionId && sessionIdRef.current === streamSessionId) {
2179
1516
  sessionIdRef.current = newSessionId;
1517
+ setCurrentSessionId(newSessionId);
2180
1518
  }
2181
1519
  },
2182
1520
  [startStream]
2183
1521
  );
2184
1522
  const clearMessages = react.useCallback(() => {
2185
- if (configRef.current.userId) {
2186
- chatStore.delete(configRef.current.userId);
2187
- }
1523
+ chatStore.delete(scopeKeyRef.current);
2188
1524
  setMessages([]);
2189
1525
  }, []);
2190
1526
  const prependMessages = react.useCallback((msgs) => {
2191
1527
  setMessages((prev) => [...msgs, ...prev]);
2192
1528
  }, []);
2193
1529
  const cancelStream = react.useCallback(() => {
2194
- if (configRef.current.userId) {
2195
- activeStreamStore.abort(configRef.current.userId);
1530
+ const scope = scopeKeyRef.current;
1531
+ const viewSid = sessionIdRef.current;
1532
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1533
+ if (activeStreamStore.has(viewStreamKey)) {
1534
+ activeStreamStore.abort(viewStreamKey);
2196
1535
  }
2197
1536
  cancelStreamManager();
2198
1537
  setIsWaitingForResponse(false);
@@ -2202,10 +1541,7 @@ function useChatV2(config, callbacks = {}) {
2202
1541
  if (msg.isStreaming) {
2203
1542
  return {
2204
1543
  ...msg,
2205
- ...createCancelledMessageUpdate(
2206
- msg.steps || [],
2207
- msg.currentMessage
2208
- )
1544
+ ...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
2209
1545
  };
2210
1546
  }
2211
1547
  return msg;
@@ -2213,39 +1549,37 @@ function useChatV2(config, callbacks = {}) {
2213
1549
  );
2214
1550
  }, [cancelStreamManager]);
2215
1551
  const resetSession = react.useCallback(() => {
2216
- if (configRef.current.userId) {
2217
- activeStreamStore.abort(configRef.current.userId);
2218
- chatStore.delete(configRef.current.userId);
1552
+ const scope = scopeKeyRef.current;
1553
+ const viewSid = sessionIdRef.current;
1554
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1555
+ if (activeStreamStore.has(viewStreamKey)) {
1556
+ activeStreamStore.abort(viewStreamKey);
2219
1557
  }
1558
+ chatStore.delete(scope);
2220
1559
  setMessages([]);
2221
1560
  sessionIdRef.current = void 0;
1561
+ setCurrentSessionId(void 0);
1562
+ activeStreamSessionRef.current = void 0;
2222
1563
  abortControllerRef.current?.abort();
2223
1564
  setIsWaitingForResponse(false);
2224
1565
  setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2225
1566
  }, []);
2226
- const getSessionId = react.useCallback(() => {
2227
- return sessionIdRef.current;
1567
+ const getSessionId = react.useCallback(() => sessionIdRef.current, []);
1568
+ const getMessages = react.useCallback(() => messages, [messages]);
1569
+ const approveUserAction = react.useCallback(async (otp) => {
1570
+ const request = userActionStateRef.current.request;
1571
+ if (!request) return;
1572
+ try {
1573
+ await submitUserAction(configRef.current, request.userActionId, { otp });
1574
+ } catch (error) {
1575
+ setUserActionState((prev) => ({
1576
+ ...prev,
1577
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1578
+ }));
1579
+ callbacksRef.current.onError?.(error);
1580
+ throw error;
1581
+ }
2228
1582
  }, []);
2229
- const getMessages = react.useCallback(() => {
2230
- return messages;
2231
- }, [messages]);
2232
- const approveUserAction = react.useCallback(
2233
- async (otp) => {
2234
- const request = userActionStateRef.current.request;
2235
- if (!request) return;
2236
- try {
2237
- await submitUserAction(configRef.current, request.userActionId, { otp });
2238
- } catch (error) {
2239
- setUserActionState((prev) => ({
2240
- ...prev,
2241
- clearOtpTrigger: prev.clearOtpTrigger + 1
2242
- }));
2243
- callbacksRef.current.onError?.(error);
2244
- throw error;
2245
- }
2246
- },
2247
- []
2248
- );
2249
1583
  const rejectUserAction = react.useCallback(async () => {
2250
1584
  const request = userActionStateRef.current.request;
2251
1585
  if (!request) return;
@@ -2279,43 +1613,91 @@ function useChatV2(config, callbacks = {}) {
2279
1613
  throw error;
2280
1614
  }
2281
1615
  }, []);
1616
+ const inFlightLoadRef = react.useRef(null);
1617
+ const loadingSessionIdRef = react.useRef(void 0);
1618
+ loadingSessionIdRef.current = loadingSessionId;
1619
+ const loadSession = react.useCallback(async (sessionId) => {
1620
+ const inFlight = inFlightLoadRef.current;
1621
+ if (inFlight && inFlight.sessionId === sessionId) {
1622
+ return inFlight.promise;
1623
+ }
1624
+ if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
1625
+ return;
1626
+ }
1627
+ const run = async () => {
1628
+ const scope = scopeKeyRef.current;
1629
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1630
+ sessionIdRef.current = sessionId;
1631
+ setCurrentSessionId(sessionId);
1632
+ callbacksRef.current.onSessionIdChange?.(sessionId);
1633
+ const streamKey = streamKeyFor(scope, sessionId);
1634
+ const active = activeStreamStore.get(streamKey);
1635
+ if (active) {
1636
+ setMessages(active.messages);
1637
+ setIsWaitingForResponse(active.isWaiting);
1638
+ setLoadingSessionId(void 0);
1639
+ return;
1640
+ }
1641
+ setIsWaitingForResponse(false);
1642
+ setMessages([]);
1643
+ chatStore.delete(scope);
1644
+ setLoadingSessionId(sessionId);
1645
+ try {
1646
+ const response = await listConversations(configRef.current, { sessionId });
1647
+ const entries = response.data ?? [];
1648
+ const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
1649
+ if (sessionIdRef.current === sessionId) {
1650
+ setMessages(historical);
1651
+ }
1652
+ if (historical.length > 0) {
1653
+ chatStore.set(scope, historical);
1654
+ }
1655
+ } catch (error) {
1656
+ callbacksRef.current.onError?.(error);
1657
+ throw error;
1658
+ } finally {
1659
+ setLoadingSessionId((current) => current === sessionId ? void 0 : current);
1660
+ }
1661
+ };
1662
+ const promise = run().finally(() => {
1663
+ if (inFlightLoadRef.current?.sessionId === sessionId) {
1664
+ inFlightLoadRef.current = null;
1665
+ }
1666
+ });
1667
+ inFlightLoadRef.current = { sessionId, promise };
1668
+ return promise;
1669
+ }, []);
2282
1670
  react.useEffect(() => {
2283
- const { userId } = config;
2284
- if (!userId) return;
2285
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1671
+ const key = streamKeyFor(scopeKey, currentSessionId);
1672
+ const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
2286
1673
  setMessages(msgs);
2287
1674
  setIsWaitingForResponse(isWaiting);
2288
1675
  });
2289
- const active = activeStreamStore.get(userId);
1676
+ const active = activeStreamStore.get(key);
2290
1677
  if (active) {
2291
1678
  setMessages(active.messages);
2292
1679
  setIsWaitingForResponse(active.isWaiting);
2293
1680
  }
2294
1681
  return unsubscribe;
2295
- }, []);
1682
+ }, [scopeKey, currentSessionId]);
2296
1683
  react.useEffect(() => {
2297
- if (!config.userId) return;
2298
1684
  const toSave = messages.filter((m) => !m.isStreaming);
2299
1685
  if (toSave.length > 0) {
2300
- chatStore.set(config.userId, toSave);
1686
+ chatStore.set(scopeKey, toSave);
2301
1687
  }
2302
- }, [messages, config.userId]);
1688
+ }, [messages, scopeKey]);
2303
1689
  react.useEffect(() => {
2304
- const prevUserId = prevUserIdRef.current;
2305
- prevUserIdRef.current = config.userId;
2306
- if (prevUserId === config.userId) return;
2307
- if (prevUserId && !config.userId) {
2308
- chatStore.delete(prevUserId);
2309
- setMessages([]);
2310
- sessionIdRef.current = void 0;
2311
- setIsWaitingForResponse(false);
2312
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2313
- } else if (config.userId) {
2314
- const stored = chatStore.get(config.userId);
2315
- setMessages(stored);
2316
- sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
2317
- }
2318
- }, [config.userId]);
1690
+ const prevKey = prevScopeKeyRef.current;
1691
+ prevScopeKeyRef.current = scopeKey;
1692
+ if (prevKey === scopeKey) return;
1693
+ const stored = chatStore.get(scopeKey);
1694
+ setMessages(stored);
1695
+ const restoredSessionId = stored.find((m) => m.sessionId)?.sessionId ?? configRef.current.session?.initialId ?? void 0;
1696
+ sessionIdRef.current = restoredSessionId;
1697
+ setCurrentSessionId(restoredSessionId);
1698
+ setIsWaitingForResponse(false);
1699
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1700
+ }, [scopeKey]);
2319
1701
  return {
2320
1702
  messages,
2321
1703
  sendMessage,
@@ -2326,11 +1708,13 @@ function useChatV2(config, callbacks = {}) {
2326
1708
  getSessionId,
2327
1709
  getMessages,
2328
1710
  isWaitingForResponse,
2329
- sessionId: sessionIdRef.current,
1711
+ sessionId: currentSessionId,
2330
1712
  userActionState,
2331
1713
  approveUserAction,
2332
1714
  rejectUserAction,
2333
- resendOtp
1715
+ resendOtp,
1716
+ loadSession,
1717
+ loadingSessionId
2334
1718
  };
2335
1719
  }
2336
1720
  function useVoice() {
@@ -2450,14 +1834,16 @@ function useVoice() {
2450
1834
  }
2451
1835
 
2452
1836
  exports.buildFormattedThinking = buildFormattedThinking;
1837
+ exports.buildScopeKey = buildScopeKey;
2453
1838
  exports.cancelUserAction = cancelUserAction;
2454
1839
  exports.createInitialV2State = createInitialV2State;
2455
1840
  exports.generateId = generateId;
1841
+ exports.listConversations = listConversations;
1842
+ exports.listSessions = listSessions;
2456
1843
  exports.processStreamEventV2 = processStreamEventV2;
2457
1844
  exports.resendUserAction = resendUserAction;
2458
1845
  exports.streamWorkflowEvents = streamWorkflowEvents;
2459
1846
  exports.submitUserAction = submitUserAction;
2460
- exports.useChat = useChat;
2461
1847
  exports.useChatV2 = useChatV2;
2462
1848
  exports.useVoice = useVoice;
2463
1849
  exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;