@paymanai/payman-typescript-ask-sdk 1.2.10 → 2.0.0

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() {
@@ -194,6 +194,14 @@ function getEventMessage(event) {
194
194
  return eventType;
195
195
  }
196
196
  }
197
+ function workingPhaseDetailForDisplay(raw) {
198
+ const t = raw.trim();
199
+ if (!t) return "";
200
+ if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
201
+ return "";
202
+ }
203
+ return t;
204
+ }
197
205
  function extractResponseContent(response) {
198
206
  if (typeof response === "string") {
199
207
  return response;
@@ -216,290 +224,8 @@ function extractResponseContent(response) {
216
224
  }
217
225
  return "";
218
226
  }
219
- function completeLastInProgressStep(steps) {
220
- for (let i = steps.length - 1; i >= 0; i--) {
221
- if (steps[i].status === "in_progress") {
222
- steps[i].status = "completed";
223
- return;
224
- }
225
- }
226
- }
227
- function processStreamEvent(event, state) {
228
- const eventType = event.eventType;
229
- if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
230
- return state;
231
- }
232
- const message = getEventMessage(event);
233
- if (eventType !== "INTENT_THINKING" && eventType !== "INTENT_THINKING_CONT") {
234
- if (state.currentThinkingStepId) {
235
- const thinkingStep = state.steps.find((s) => s.id === state.currentThinkingStepId);
236
- if (thinkingStep) {
237
- thinkingStep.isThinking = false;
238
- }
239
- state.currentThinkingStepId = void 0;
240
- }
241
- state.activeThinkingText = void 0;
242
- }
243
- if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
244
- let content = extractResponseContent(event.response);
245
- const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
246
- if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
247
- content = trace.workflowMsg;
248
- }
249
- if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
250
- const agg = trace.aggregator;
251
- if (typeof agg.response === "string") content = agg.response;
252
- else content = extractResponseContent(agg.response);
253
- }
254
- if (content) {
255
- state.accumulatedContent = content;
256
- state.finalData = event.response ?? event.trace;
257
- state.hasError = false;
258
- state.errorMessage = "";
259
- } else {
260
- state.hasError = true;
261
- state.errorMessage = "WORKFLOW_FAILED";
262
- }
263
- }
264
- if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
265
- state.steps.forEach((step) => {
266
- if (step.status === "in_progress") {
267
- step.status = "completed";
268
- }
269
- });
270
- } else if (eventType === "INTENT_ERROR") {
271
- state.errorMessage = message || event.errorMessage || "An error occurred";
272
- const intentStep = state.steps.find(
273
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
274
- );
275
- if (intentStep) {
276
- intentStep.status = "error";
277
- }
278
- } else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
279
- state.hasError = true;
280
- state.errorMessage = message || event.errorMessage || "An error occurred";
281
- } else if (eventType === "ORCHESTRATOR_COMPLETED") {
282
- state.inOrchestratorPhase = false;
283
- const orchestratorStep = state.steps.find(
284
- (s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
285
- );
286
- if (orchestratorStep) {
287
- orchestratorStep.status = "completed";
288
- if (event.elapsedMs) {
289
- orchestratorStep.elapsedMs = event.elapsedMs;
290
- }
291
- if (orchestratorStep.id === state.currentExecutingStepId) {
292
- state.currentExecutingStepId = void 0;
293
- }
294
- }
295
- } else if (eventType === "INTENT_COMPLETED") {
296
- const intentStep = state.steps.find(
297
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
298
- );
299
- if (intentStep) {
300
- intentStep.status = "completed";
301
- if (event.elapsedMs) {
302
- intentStep.elapsedMs = event.elapsedMs;
303
- }
304
- if (intentStep.id === state.currentExecutingStepId) {
305
- state.currentExecutingStepId = void 0;
306
- }
307
- }
308
- } else if (eventType === "AGGREGATOR_COMPLETED") {
309
- state.inAggregatorPhase = false;
310
- const aggregatorStep = state.steps.find(
311
- (s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
312
- );
313
- if (aggregatorStep) {
314
- aggregatorStep.status = "completed";
315
- if (event.elapsedMs) {
316
- aggregatorStep.elapsedMs = event.elapsedMs;
317
- }
318
- if (aggregatorStep.id === state.currentExecutingStepId) {
319
- state.currentExecutingStepId = void 0;
320
- }
321
- }
322
- } else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
323
- if (eventType === "ORCHESTRATOR_THINKING") {
324
- state.inOrchestratorPhase = true;
325
- }
326
- if (eventType === "AGGREGATOR_THINKING") {
327
- state.inAggregatorPhase = true;
328
- }
329
- if (eventType === "INTENT_PROGRESS") {
330
- const intentStep = state.steps.find(
331
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
332
- );
333
- if (intentStep) {
334
- intentStep.message = message;
335
- state.currentExecutingStepId = intentStep.id;
336
- } else {
337
- const stepId = `step-${state.stepCounter++}`;
338
- state.steps.push({
339
- id: stepId,
340
- eventType: "INTENT_STARTED",
341
- message,
342
- status: "in_progress",
343
- timestamp: Date.now(),
344
- elapsedMs: event.elapsedMs
345
- });
346
- state.currentExecutingStepId = stepId;
347
- }
348
- } else {
349
- const stepId = `step-${state.stepCounter++}`;
350
- state.steps.push({
351
- id: stepId,
352
- eventType,
353
- message,
354
- status: "in_progress",
355
- timestamp: Date.now(),
356
- elapsedMs: event.elapsedMs
357
- });
358
- state.currentExecutingStepId = stepId;
359
- }
360
- } else if (eventType === "USER_ACTION_REQUIRED") {
361
- completeLastInProgressStep(state.steps);
362
- if (event.userActionRequest) {
363
- state.userActionRequest = {
364
- userActionId: event.userActionRequest.userActionId,
365
- userActionType: event.userActionRequest.userActionType,
366
- message: event.userActionRequest.message,
367
- requestedSchema: event.userActionRequest.requestedSchema,
368
- metadata: event.userActionRequest.metadata
369
- };
370
- }
371
- state.userActionPending = true;
372
- const stepId = `step-${state.stepCounter++}`;
373
- state.steps.push({
374
- id: stepId,
375
- eventType,
376
- message,
377
- status: "in_progress",
378
- timestamp: Date.now(),
379
- elapsedMs: event.elapsedMs
380
- });
381
- state.currentExecutingStepId = stepId;
382
- } else if (eventType === "USER_ACTION_SUCCESS") {
383
- completeLastInProgressStep(state.steps);
384
- state.userActionRequest = void 0;
385
- state.userActionPending = false;
386
- state.userActionResult = "approved";
387
- const stepId = `step-${state.stepCounter++}`;
388
- state.steps.push({
389
- id: stepId,
390
- eventType,
391
- message,
392
- status: "completed",
393
- timestamp: Date.now(),
394
- elapsedMs: event.elapsedMs
395
- });
396
- } else if (eventType === "USER_ACTION_INVALID") {
397
- completeLastInProgressStep(state.steps);
398
- const errorStepId = `step-${state.stepCounter++}`;
399
- state.steps.push({
400
- id: errorStepId,
401
- eventType,
402
- message,
403
- status: "error",
404
- timestamp: Date.now(),
405
- elapsedMs: event.elapsedMs
406
- });
407
- const retryStepId = `step-${state.stepCounter++}`;
408
- state.steps.push({
409
- id: retryStepId,
410
- eventType: "USER_ACTION_REQUIRED",
411
- message: "Waiting for verification...",
412
- status: "in_progress",
413
- timestamp: Date.now()
414
- });
415
- state.currentExecutingStepId = retryStepId;
416
- } else if (eventType === "USER_ACTION_EXPIRED") {
417
- completeLastInProgressStep(state.steps);
418
- state.userActionRequest = void 0;
419
- state.userActionPending = false;
420
- const stepId = `step-${state.stepCounter++}`;
421
- state.steps.push({
422
- id: stepId,
423
- eventType,
424
- message,
425
- status: "error",
426
- timestamp: Date.now(),
427
- elapsedMs: event.elapsedMs
428
- });
429
- } else if (eventType === "USER_ACTION_REJECTED") {
430
- completeLastInProgressStep(state.steps);
431
- state.userActionRequest = void 0;
432
- state.userActionPending = false;
433
- state.userActionResult = "rejected";
434
- const stepId = `step-${state.stepCounter++}`;
435
- state.steps.push({
436
- id: stepId,
437
- eventType,
438
- message,
439
- status: "completed",
440
- timestamp: Date.now(),
441
- elapsedMs: event.elapsedMs
442
- });
443
- } else if (eventType === "USER_ACTION_RESENT") {
444
- const stepId = `step-${state.stepCounter++}`;
445
- state.steps.push({
446
- id: stepId,
447
- eventType,
448
- message,
449
- status: "completed",
450
- timestamp: Date.now(),
451
- elapsedMs: event.elapsedMs
452
- });
453
- } else if (eventType === "USER_ACTION_FAILED") {
454
- completeLastInProgressStep(state.steps);
455
- state.userActionRequest = void 0;
456
- state.userActionPending = false;
457
- const stepId = `step-${state.stepCounter++}`;
458
- state.steps.push({
459
- id: stepId,
460
- eventType,
461
- message,
462
- status: "error",
463
- timestamp: Date.now(),
464
- elapsedMs: event.elapsedMs
465
- });
466
- } else if (eventType === "INTENT_THINKING") {
467
- if (state.currentThinkingStepId) {
468
- const prev = state.steps.find((s) => s.id === state.currentThinkingStepId);
469
- if (prev) prev.isThinking = false;
470
- }
471
- const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
472
- if (lastInProgress) {
473
- lastInProgress.thinkingText = "";
474
- lastInProgress.isThinking = true;
475
- state.currentThinkingStepId = lastInProgress.id;
476
- } else {
477
- state.currentThinkingStepId = void 0;
478
- }
479
- if (state.allThinkingText) state.allThinkingText += "\n\n";
480
- if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
481
- state.activeThinkingText = "";
482
- }
483
- } else if (eventType === "INTENT_THINKING_CONT") {
484
- const delta = event.message || "";
485
- if (!delta) return state;
486
- if (state.currentThinkingStepId) {
487
- const step = state.steps.find((s) => s.id === state.currentThinkingStepId);
488
- if (step) {
489
- step.thinkingText = (step.thinkingText || "") + delta;
490
- }
491
- }
492
- state.allThinkingText += delta;
493
- if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
494
- if (state.activeThinkingText == null) state.activeThinkingText = "";
495
- state.activeThinkingText += delta;
496
- }
497
- }
498
- return state;
499
- }
500
227
 
501
228
  // src/utils/messageStateManager.ts
502
- var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
503
229
  function buildFormattedThinking(steps, allThinkingText) {
504
230
  const parts = [];
505
231
  const safeSteps = steps ?? [];
@@ -529,6 +255,12 @@ function buildFormattedThinking(steps, allThinkingText) {
529
255
  parts.push("**Planning**");
530
256
  if (step.message) parts.push(step.message);
531
257
  break;
258
+ case "WORKING": {
259
+ const detail = workingPhaseDetailForDisplay(step.message || "");
260
+ parts.push("**Working**");
261
+ if (detail) parts.push(detail);
262
+ break;
263
+ }
532
264
  case "INTENT_STARTED": {
533
265
  let label = step.message || "Processing";
534
266
  const started = label.match(/^(.+?)\s+started$/i);
@@ -568,73 +300,6 @@ function buildFormattedThinking(steps, allThinkingText) {
568
300
  }
569
301
  return parts.length > 0 ? parts.join("\n") : allThinkingText;
570
302
  }
571
- function createStreamingMessageUpdate(state) {
572
- const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
573
- const steps = state.hasError ? [] : [...state.steps];
574
- const allThinking = state.hasError ? void 0 : state.allThinkingText;
575
- return {
576
- streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
577
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
578
- currentMessage: state.hasError ? void 0 : state.currentMessage,
579
- streamProgress: state.hasError ? "error" : "processing",
580
- isError: state.hasError,
581
- errorDetails: state.hasError ? state.errorMessage : void 0,
582
- executionId: state.executionId,
583
- sessionId: state.sessionId,
584
- steps,
585
- currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
586
- isCancelled: false,
587
- userActionResult: state.userActionResult,
588
- activeThinkingText: state.hasError ? void 0 : state.activeThinkingText,
589
- allThinkingText: allThinking,
590
- formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || "")
591
- };
592
- }
593
- function createErrorMessageUpdate(error, state) {
594
- const isAborted = error.name === "AbortError";
595
- return {
596
- isStreaming: false,
597
- streamProgress: isAborted ? "processing" : "error",
598
- isError: !isAborted,
599
- isCancelled: isAborted,
600
- errorDetails: isAborted ? void 0 : error.message,
601
- content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
602
- // Preserve currentMessage when cancelled so UI can show it
603
- currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
604
- steps: [...state.steps].map((step) => {
605
- if (step.status === "in_progress" && isAborted) {
606
- return { ...step, status: "pending" };
607
- }
608
- return step;
609
- }),
610
- currentExecutingStepId: void 0
611
- };
612
- }
613
- function createFinalMessage(streamingId, state) {
614
- const steps = state.hasError ? [] : [...state.steps];
615
- const allThinking = state.hasError ? void 0 : state.allThinkingText;
616
- return {
617
- id: streamingId,
618
- sessionId: state.sessionId,
619
- role: "assistant",
620
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
621
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
622
- isStreaming: false,
623
- streamProgress: state.hasError ? "error" : "completed",
624
- isError: state.hasError,
625
- errorDetails: state.hasError ? state.errorMessage : void 0,
626
- executionId: state.executionId,
627
- tracingData: state.finalData,
628
- steps,
629
- isCancelled: false,
630
- currentExecutingStepId: void 0,
631
- userActionResult: state.userActionResult,
632
- activeThinkingText: void 0,
633
- allThinkingText: allThinking,
634
- formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || ""),
635
- isResolvingImages: state.hasError ? void 0 : state.isResolvingImages
636
- };
637
- }
638
303
  function createCancelledMessageUpdate(steps, currentMessage) {
639
304
  const updatedSteps = steps.map((step) => {
640
305
  if (step.status === "in_progress") {
@@ -654,35 +319,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
654
319
 
655
320
  // src/utils/requestBuilder.ts
656
321
  function buildRequestBody(config, userMessage, sessionId) {
657
- const sessionOwner = config.sessionParams;
658
- const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
322
+ const owner = config.session?.owner;
323
+ const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
659
324
  return {
660
- workflowName: config.workflowName,
325
+ workflowName: config.workflow.name,
661
326
  userInput: userMessage,
662
327
  sessionId,
663
- sessionOwnerId: sessionOwner?.id || "",
664
- sessionOwnerLabel: sessionOwner?.name || "",
328
+ sessionOwnerId: owner?.id || "",
329
+ sessionOwnerLabel: owner?.name || "",
665
330
  sessionAttributes,
666
331
  options: {
667
332
  clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
668
333
  }
669
334
  };
670
335
  }
336
+ function getStageParamName(config) {
337
+ return config.api.stageQueryParam ?? "stage";
338
+ }
339
+ function getStage(config) {
340
+ return config.workflow.stage ?? "DEV";
341
+ }
671
342
  function buildStreamingUrl(config) {
672
343
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
673
- const stage = config.stage || "DEV";
674
- const stageParamName = config.stageQueryParam ?? "stage";
675
- const queryParams = new URLSearchParams({ [stageParamName]: stage });
676
- if (config.workflowVersion !== void 0) {
677
- queryParams.append("workflowVersion", String(config.workflowVersion));
344
+ const queryParams = new URLSearchParams({
345
+ [getStageParamName(config)]: getStage(config)
346
+ });
347
+ if (config.workflow.version !== void 0) {
348
+ queryParams.append("workflowVersion", String(config.workflow.version));
678
349
  }
679
350
  return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
680
351
  }
681
- function buildUserActionUrl(config, userActionId, action) {
352
+ function deriveBasePath(config) {
682
353
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
683
354
  const [endpointPath] = endpoint.split("?");
684
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
685
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
355
+ const normalized = endpointPath.replace(/\/+$/, "");
356
+ return normalized.endsWith("/stream") ? normalized.slice(0, -"/stream".length) : normalized;
357
+ }
358
+ function buildUserActionUrl(config, userActionId, action) {
359
+ const basePath = deriveBasePath(config);
686
360
  const encodedUserActionId = encodeURIComponent(userActionId);
687
361
  return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
688
362
  }
@@ -690,21 +364,23 @@ function buildResolveImagesUrl(config) {
690
364
  if (config.api.resolveImagesEndpoint) {
691
365
  return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
692
366
  }
693
- const streamEndpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
694
- const [endpointPath] = streamEndpoint.split("?");
695
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
696
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
697
- return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
367
+ return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
698
368
  }
699
369
  function buildRequestHeaders(config) {
700
- const headers = {
701
- ...config.api.headers
702
- };
370
+ const headers = { ...config.api.headers };
703
371
  if (config.api.authToken) {
704
372
  headers.Authorization = `Bearer ${config.api.authToken}`;
705
373
  }
706
374
  return headers;
707
375
  }
376
+ function buildScopeKey(config) {
377
+ return [
378
+ config.session?.userId ?? "",
379
+ config.workflow.id ?? "",
380
+ config.workflow.version ?? "",
381
+ config.workflow.stage ?? ""
382
+ ].join("|");
383
+ }
708
384
 
709
385
  // src/utils/userActionClient.ts
710
386
  async function sendUserActionRequest(config, userActionId, action, data) {
@@ -716,691 +392,104 @@ async function sendUserActionRequest(config, userActionId, action, data) {
716
392
  method: "POST",
717
393
  headers,
718
394
  body: hasBody ? JSON.stringify(data) : void 0
719
- });
720
- if (!response.ok) {
721
- const errorText = await response.text();
722
- throw new Error(`HTTP ${response.status}: ${errorText}`);
723
- }
724
- return await response.json();
725
- }
726
- async function submitUserAction(config, userActionId, data) {
727
- return sendUserActionRequest(config, userActionId, "submit", data);
728
- }
729
- async function cancelUserAction(config, userActionId) {
730
- return sendUserActionRequest(config, userActionId, "cancel");
731
- }
732
- async function resendUserAction(config, userActionId) {
733
- return sendUserActionRequest(config, userActionId, "resend");
734
- }
735
- var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
736
- var chatStore = {
737
- get(key) {
738
- const raw = storage.getString(key);
739
- if (!raw) return [];
740
- try {
741
- return JSON.parse(raw);
742
- } catch {
743
- return [];
744
- }
745
- },
746
- set(key, messages) {
747
- storage.set(key, JSON.stringify(messages));
748
- },
749
- delete(key) {
750
- storage.delete(key);
751
- }
752
- };
753
-
754
- // src/utils/activeStreamStore.ts
755
- var streams = /* @__PURE__ */ new Map();
756
- var activeStreamStore = {
757
- has(key) {
758
- return streams.has(key);
759
- },
760
- get(key) {
761
- const entry = streams.get(key);
762
- if (!entry) return null;
763
- return { messages: entry.messages, isWaiting: entry.isWaiting };
764
- },
765
- // Called before startStream — registers the controller and initial messages
766
- start(key, abortController, initialMessages) {
767
- const existing = streams.get(key);
768
- streams.set(key, {
769
- messages: initialMessages,
770
- isWaiting: true,
771
- abortController,
772
- listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
773
- });
774
- },
775
- // Called by the stream on every event — applies the same updater pattern React uses
776
- applyMessages(key, updater) {
777
- const entry = streams.get(key);
778
- if (!entry) return;
779
- const next = typeof updater === "function" ? updater(entry.messages) : updater;
780
- entry.messages = next;
781
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
782
- },
783
- setWaiting(key, waiting) {
784
- const entry = streams.get(key);
785
- if (!entry) return;
786
- entry.isWaiting = waiting;
787
- entry.listeners.forEach((l) => l(entry.messages, waiting));
788
- },
789
- // Called when stream completes — persists to chatStore and cleans up
790
- complete(key) {
791
- const entry = streams.get(key);
792
- if (!entry) return;
793
- entry.isWaiting = false;
794
- entry.listeners.forEach((l) => l(entry.messages, false));
795
- const toSave = entry.messages.filter((m) => !m.isStreaming);
796
- if (toSave.length > 0) chatStore.set(key, toSave);
797
- streams.delete(key);
798
- },
799
- // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
800
- subscribe(key, listener) {
801
- const entry = streams.get(key);
802
- if (!entry) return () => {
803
- };
804
- entry.listeners.add(listener);
805
- return () => {
806
- streams.get(key)?.listeners.delete(listener);
807
- };
808
- },
809
- // Explicit user cancel — aborts the controller and removes the entry
810
- abort(key) {
811
- const entry = streams.get(key);
812
- if (!entry) return;
813
- entry.abortController.abort();
814
- streams.delete(key);
815
- }
816
- };
817
-
818
- // src/utils/ragImageResolver.ts
819
- var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
820
- function hasRagImages(content) {
821
- return RAG_IMAGE_REGEX.test(content);
822
- }
823
- async function waitForNextPaint(signal) {
824
- if (signal?.aborted) return;
825
- await new Promise((resolve) => {
826
- let isSettled = false;
827
- const finish = () => {
828
- if (isSettled) return;
829
- isSettled = true;
830
- signal?.removeEventListener("abort", finish);
831
- resolve();
832
- };
833
- signal?.addEventListener("abort", finish, { once: true });
834
- if (typeof requestAnimationFrame === "function") {
835
- requestAnimationFrame(() => {
836
- setTimeout(finish, 0);
837
- });
838
- return;
839
- }
840
- setTimeout(finish, 0);
841
- });
842
- }
843
- async function resolveRagImageUrls(config, content, signal) {
844
- const url = buildResolveImagesUrl(config);
845
- const baseHeaders = buildRequestHeaders(config);
846
- const headers = { "Content-Type": "application/json", ...baseHeaders };
847
- const response = await fetch(url, {
848
- method: "POST",
849
- headers,
850
- body: JSON.stringify({ input: content }),
851
- signal
852
- });
853
- if (!response.ok) {
854
- const errorText = await response.text();
855
- throw new Error(`HTTP ${response.status}: ${errorText}`);
856
- }
857
- const text = await response.text();
858
- try {
859
- const parsed = JSON.parse(text);
860
- if (typeof parsed === "string") return parsed;
861
- if (typeof parsed.output === "string") return parsed.output;
862
- if (typeof parsed.result === "string") return parsed.result;
863
- } catch {
864
- }
865
- return text;
866
- }
867
-
868
- // src/hooks/useStreamManager.ts
869
- function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
870
- const abortControllerRef = react.useRef(null);
871
- const configRef = react.useRef(config);
872
- configRef.current = config;
873
- const callbacksRef = react.useRef(callbacks);
874
- callbacksRef.current = callbacks;
875
- const startStream = react.useCallback(
876
- async (userMessage, streamingId, sessionId, externalAbortController) => {
877
- abortControllerRef.current?.abort();
878
- const abortController = externalAbortController ?? new AbortController();
879
- abortControllerRef.current = abortController;
880
- const state = {
881
- accumulatedContent: "",
882
- executionId: void 0,
883
- currentSessionId: void 0,
884
- finalData: void 0,
885
- steps: [],
886
- stepCounter: 0,
887
- currentExecutingStepId: void 0,
888
- currentThinkingStepId: void 0,
889
- hasError: false,
890
- errorMessage: "",
891
- userActionRequest: void 0,
892
- userActionPending: false,
893
- userActionResult: void 0,
894
- allThinkingText: "",
895
- inOrchestratorPhase: false,
896
- inAggregatorPhase: false
897
- };
898
- const THROTTLE_MS = 120;
899
- const CHARS_PER_TICK = 10;
900
- const displayedLengthRef = { current: 0 };
901
- let throttleIntervalId = null;
902
- const getActiveStepMessage = () => state.steps.find((s) => s.id === state.currentExecutingStepId)?.message || [...state.steps].reverse().find((s) => s.status === "in_progress")?.message;
903
- const advanceDisplayLength = (full) => {
904
- let newLen = Math.min(displayedLengthRef.current + CHARS_PER_TICK, full.length);
905
- if (newLen > 0 && newLen < full.length) {
906
- const code = full.charCodeAt(newLen - 1);
907
- if (code >= 55296 && code <= 56319) {
908
- newLen = Math.min(newLen + 1, full.length);
909
- }
910
- }
911
- displayedLengthRef.current = newLen;
912
- };
913
- const clearThrottle = () => {
914
- if (throttleIntervalId != null) {
915
- clearInterval(throttleIntervalId);
916
- throttleIntervalId = null;
917
- }
918
- };
919
- abortController.signal.addEventListener("abort", clearThrottle, { once: true });
920
- const ensureThrottle = () => {
921
- if (throttleIntervalId != null) return;
922
- throttleIntervalId = setInterval(() => {
923
- if (abortController.signal.aborted) {
924
- clearThrottle();
925
- return;
926
- }
927
- const full = state.activeThinkingText ?? "";
928
- if (displayedLengthRef.current < full.length) {
929
- advanceDisplayLength(full);
930
- const displayText = full.slice(0, displayedLengthRef.current);
931
- setMessages(
932
- (prev) => prev.map(
933
- (msg) => msg.id === streamingId ? {
934
- ...msg,
935
- ...createStreamingMessageUpdate({
936
- ...state,
937
- activeThinkingText: displayText,
938
- currentMessage: getActiveStepMessage() || "Thinking..."
939
- })
940
- } : msg
941
- )
942
- );
943
- }
944
- }, THROTTLE_MS);
945
- };
946
- const currentConfig = configRef.current;
947
- const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
948
- const url = buildStreamingUrl(currentConfig);
949
- const headers = buildRequestHeaders(currentConfig);
950
- try {
951
- await streamWorkflowEvents(url, requestBody, headers, {
952
- signal: abortController.signal,
953
- onEvent: (event) => {
954
- if (abortController.signal.aborted) {
955
- return;
956
- }
957
- if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
958
- if (event.executionId) state.executionId = event.executionId;
959
- if (event.sessionId) state.currentSessionId = event.sessionId;
960
- return;
961
- }
962
- if (event.executionId) state.executionId = event.executionId;
963
- if (event.sessionId) state.currentSessionId = event.sessionId;
964
- const activeThinkingLengthBeforeProcess = state.activeThinkingText?.length ?? 0;
965
- processStreamEvent(event, state);
966
- const eventType = event.eventType;
967
- if (eventType === "INTENT_THINKING") {
968
- displayedLengthRef.current = 0;
969
- ensureThrottle();
970
- } else if (eventType !== "INTENT_THINKING_CONT") {
971
- displayedLengthRef.current = activeThinkingLengthBeforeProcess;
972
- clearThrottle();
973
- }
974
- if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
975
- callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
976
- } else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
977
- const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
978
- callbacksRef.current.onUserActionEvent?.(eventType, msg);
979
- }
980
- const isIntentThinkingEvent = eventType === "INTENT_THINKING" || eventType === "INTENT_THINKING_CONT";
981
- const rawMessage = event.message?.trim() || event.errorMessage?.trim();
982
- const currentMessage = isIntentThinkingEvent ? getActiveStepMessage() || "Thinking..." : rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : getActiveStepMessage() || getEventMessage(event));
983
- const displayThinking = state.activeThinkingText != null ? state.activeThinkingText.slice(0, displayedLengthRef.current) : state.activeThinkingText;
984
- setMessages(
985
- (prev) => prev.map(
986
- (msg) => msg.id === streamingId ? {
987
- ...msg,
988
- ...createStreamingMessageUpdate({
989
- ...state,
990
- activeThinkingText: displayThinking,
991
- currentMessage
992
- })
993
- } : msg
994
- )
995
- );
996
- },
997
- onError: (error) => {
998
- clearThrottle();
999
- setIsWaitingForResponse(false);
1000
- if (error.name !== "AbortError") {
1001
- callbacksRef.current.onError?.(error);
1002
- }
1003
- if (state.userActionPending) {
1004
- state.userActionPending = false;
1005
- state.userActionRequest = void 0;
1006
- callbacksRef.current.onUserActionEvent?.(
1007
- "USER_ACTION_FAILED",
1008
- "Connection lost. Please try again."
1009
- );
1010
- }
1011
- setMessages(
1012
- (prev) => prev.map(
1013
- (msg) => msg.id === streamingId ? {
1014
- ...msg,
1015
- ...createErrorMessageUpdate(error, state)
1016
- } : msg
1017
- )
1018
- );
1019
- },
1020
- onComplete: () => {
1021
- clearThrottle();
1022
- setIsWaitingForResponse(false);
1023
- if (state.userActionPending) {
1024
- state.userActionPending = false;
1025
- state.userActionRequest = void 0;
1026
- callbacksRef.current.onUserActionEvent?.(
1027
- "USER_ACTION_FAILED",
1028
- "Verification could not be completed."
1029
- );
1030
- }
1031
- if (state.currentSessionId && state.currentSessionId !== sessionId) {
1032
- callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
1033
- }
1034
- const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.accumulatedContent);
1035
- const finalMessage = createFinalMessage(streamingId, {
1036
- ...state,
1037
- sessionId: state.currentSessionId || sessionId,
1038
- isResolvingImages: needsImageResolve
1039
- });
1040
- setMessages(
1041
- (prev) => prev.map(
1042
- (msg) => msg.id === streamingId ? finalMessage : msg
1043
- )
1044
- );
1045
- callbacksRef.current.onStreamComplete?.(finalMessage);
1046
- }
1047
- });
1048
- clearThrottle();
1049
- const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.accumulatedContent);
1050
- if (shouldResolveImages) {
1051
- await waitForNextPaint(abortController.signal);
1052
- }
1053
- if (shouldResolveImages && !abortController.signal.aborted) {
1054
- try {
1055
- const resolvedContent = await resolveRagImageUrls(
1056
- currentConfig,
1057
- state.accumulatedContent,
1058
- abortController.signal
1059
- );
1060
- setMessages(
1061
- (prev) => prev.map(
1062
- (msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
1063
- )
1064
- );
1065
- } catch {
1066
- setMessages(
1067
- (prev) => prev.map(
1068
- (msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
1069
- )
1070
- );
1071
- }
1072
- }
1073
- return state.currentSessionId;
1074
- } catch (error) {
1075
- clearThrottle();
1076
- setIsWaitingForResponse(false);
1077
- if (error.name !== "AbortError") {
1078
- callbacksRef.current.onError?.(error);
1079
- }
1080
- if (state.userActionPending) {
1081
- state.userActionPending = false;
1082
- state.userActionRequest = void 0;
1083
- callbacksRef.current.onUserActionEvent?.(
1084
- "USER_ACTION_FAILED",
1085
- "Connection lost. Please try again."
1086
- );
1087
- }
1088
- setMessages(
1089
- (prev) => prev.map(
1090
- (msg) => msg.id === streamingId ? {
1091
- ...msg,
1092
- ...createErrorMessageUpdate(error, state)
1093
- } : msg
1094
- )
1095
- );
1096
- return state.currentSessionId;
1097
- }
1098
- },
1099
- [setMessages, setIsWaitingForResponse]
1100
- );
1101
- const cancelStream = react.useCallback(() => {
1102
- abortControllerRef.current?.abort();
1103
- }, []);
1104
- return {
1105
- startStream,
1106
- cancelStream,
1107
- abortControllerRef
1108
- };
1109
- }
1110
-
1111
- // src/hooks/useChat.ts
1112
- function useChat(config, callbacks = {}) {
1113
- const [messages, setMessages] = react.useState(() => {
1114
- if (config.userId) return chatStore.get(config.userId);
1115
- return config.initialMessages ?? [];
1116
- });
1117
- const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1118
- const sessionIdRef = react.useRef(
1119
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
1120
- );
1121
- const prevUserIdRef = react.useRef(config.userId);
1122
- const callbacksRef = react.useRef(callbacks);
1123
- callbacksRef.current = callbacks;
1124
- const configRef = react.useRef(config);
1125
- configRef.current = config;
1126
- const messagesRef = react.useRef(messages);
1127
- messagesRef.current = messages;
1128
- const storeAwareSetMessages = react.useCallback(
1129
- (updater) => {
1130
- const { userId } = configRef.current;
1131
- if (userId && activeStreamStore.has(userId)) {
1132
- activeStreamStore.applyMessages(userId, updater);
1133
- }
1134
- setMessages(updater);
1135
- },
1136
- // eslint-disable-next-line react-hooks/exhaustive-deps
1137
- []
1138
- );
1139
- const storeAwareSetIsWaiting = react.useCallback(
1140
- (waiting) => {
1141
- const { userId } = configRef.current;
1142
- if (userId && activeStreamStore.has(userId)) {
1143
- activeStreamStore.setWaiting(userId, waiting);
1144
- }
1145
- setIsWaitingForResponse(waiting);
1146
- },
1147
- // eslint-disable-next-line react-hooks/exhaustive-deps
1148
- []
1149
- );
1150
- const [userActionState, setUserActionState] = react.useState({
1151
- request: null,
1152
- result: null,
1153
- clearOtpTrigger: 0
1154
- });
1155
- const userActionStateRef = react.useRef(userActionState);
1156
- userActionStateRef.current = userActionState;
1157
- const wrappedCallbacks = react.useMemo(() => ({
1158
- ...callbacksRef.current,
1159
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1160
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1161
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1162
- onError: (error) => callbacksRef.current.onError?.(error),
1163
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1164
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1165
- onUserActionRequired: (request) => {
1166
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1167
- callbacksRef.current.onUserActionRequired?.(request);
1168
- },
1169
- onUserActionEvent: (eventType, message) => {
1170
- switch (eventType) {
1171
- case "USER_ACTION_SUCCESS":
1172
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1173
- break;
1174
- case "USER_ACTION_REJECTED":
1175
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1176
- break;
1177
- case "USER_ACTION_EXPIRED":
1178
- case "USER_ACTION_FAILED":
1179
- setUserActionState((prev) => ({ ...prev, request: null }));
1180
- break;
1181
- case "USER_ACTION_INVALID":
1182
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1183
- break;
1184
- }
1185
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1186
- }
1187
- // eslint-disable-next-line react-hooks/exhaustive-deps
1188
- }), []);
1189
- const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
1190
- config,
1191
- wrappedCallbacks,
1192
- storeAwareSetMessages,
1193
- storeAwareSetIsWaiting
1194
- );
1195
- const sendMessage = react.useCallback(
1196
- async (userMessage) => {
1197
- if (!userMessage.trim()) return;
1198
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1199
- sessionIdRef.current = generateId();
1200
- callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1201
- }
1202
- const userMessageId = `user-${Date.now()}`;
1203
- const userMsg = {
1204
- id: userMessageId,
1205
- sessionId: sessionIdRef.current,
1206
- role: "user",
1207
- content: userMessage,
1208
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1209
- };
1210
- setMessages((prev) => [...prev, userMsg]);
1211
- callbacksRef.current.onMessageSent?.(userMessage);
1212
- setIsWaitingForResponse(true);
1213
- callbacksRef.current.onStreamStart?.();
1214
- const streamingId = `assistant-${Date.now()}`;
1215
- const streamingMsg = {
1216
- id: streamingId,
1217
- sessionId: sessionIdRef.current,
1218
- role: "assistant",
1219
- content: "",
1220
- streamingContent: "",
1221
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1222
- isStreaming: true,
1223
- streamProgress: "started",
1224
- steps: [],
1225
- currentExecutingStepId: void 0,
1226
- isCancelled: false,
1227
- currentMessage: void 0
1228
- };
1229
- setMessages((prev) => [...prev, streamingMsg]);
1230
- const abortController = new AbortController();
1231
- const { userId } = configRef.current;
1232
- if (userId) {
1233
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1234
- activeStreamStore.start(userId, abortController, initialMessages);
1235
- }
1236
- const newSessionId = await startStream(
1237
- userMessage,
1238
- streamingId,
1239
- sessionIdRef.current,
1240
- abortController
1241
- );
1242
- if (userId) {
1243
- activeStreamStore.complete(userId);
1244
- }
1245
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1246
- sessionIdRef.current = newSessionId;
1247
- }
1248
- },
1249
- [startStream]
1250
- );
1251
- const clearMessages = react.useCallback(() => {
1252
- if (configRef.current.userId) {
1253
- chatStore.delete(configRef.current.userId);
1254
- }
1255
- setMessages([]);
1256
- }, []);
1257
- const prependMessages = react.useCallback((msgs) => {
1258
- setMessages((prev) => [...msgs, ...prev]);
1259
- }, []);
1260
- const cancelStream = react.useCallback(() => {
1261
- if (configRef.current.userId) {
1262
- activeStreamStore.abort(configRef.current.userId);
1263
- }
1264
- cancelStreamManager();
1265
- setIsWaitingForResponse(false);
1266
- setUserActionState((prev) => ({ ...prev, request: null, result: null }));
1267
- setMessages(
1268
- (prev) => prev.map((msg) => {
1269
- if (msg.isStreaming) {
1270
- return {
1271
- ...msg,
1272
- ...createCancelledMessageUpdate(
1273
- msg.steps || [],
1274
- msg.currentMessage
1275
- )
1276
- };
1277
- }
1278
- return msg;
1279
- })
1280
- );
1281
- }, [cancelStreamManager]);
1282
- const resetSession = react.useCallback(() => {
1283
- if (configRef.current.userId) {
1284
- activeStreamStore.abort(configRef.current.userId);
1285
- chatStore.delete(configRef.current.userId);
1286
- }
1287
- setMessages([]);
1288
- sessionIdRef.current = void 0;
1289
- abortControllerRef.current?.abort();
1290
- setIsWaitingForResponse(false);
1291
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1292
- }, []);
1293
- const getSessionId = react.useCallback(() => {
1294
- return sessionIdRef.current;
1295
- }, []);
1296
- const getMessages = react.useCallback(() => {
1297
- return messages;
1298
- }, [messages]);
1299
- const approveUserAction = react.useCallback(
1300
- async (otp) => {
1301
- const request = userActionStateRef.current.request;
1302
- if (!request) return;
1303
- try {
1304
- await submitUserAction(configRef.current, request.userActionId, { otp });
1305
- } catch (error) {
1306
- setUserActionState((prev) => ({
1307
- ...prev,
1308
- clearOtpTrigger: prev.clearOtpTrigger + 1
1309
- }));
1310
- callbacksRef.current.onError?.(error);
1311
- throw error;
1312
- }
1313
- },
1314
- []
1315
- );
1316
- const rejectUserAction = react.useCallback(async () => {
1317
- const request = userActionStateRef.current.request;
1318
- if (!request) return;
1319
- try {
1320
- setMessages((prev) => {
1321
- let lastStreamingIdx = -1;
1322
- for (let i = prev.length - 1; i >= 0; i--) {
1323
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
1324
- lastStreamingIdx = i;
1325
- break;
1326
- }
1327
- }
1328
- if (lastStreamingIdx === -1) return prev;
1329
- return prev.map(
1330
- (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
1331
- );
1332
- });
1333
- await cancelUserAction(configRef.current, request.userActionId);
1334
- } catch (error) {
1335
- callbacksRef.current.onError?.(error);
1336
- throw error;
1337
- }
1338
- }, []);
1339
- const resendOtp = react.useCallback(async () => {
1340
- const request = userActionStateRef.current.request;
1341
- if (!request) return;
1342
- try {
1343
- await resendUserAction(configRef.current, request.userActionId);
1344
- } catch (error) {
1345
- callbacksRef.current.onError?.(error);
1346
- throw error;
1347
- }
1348
- }, []);
1349
- react.useEffect(() => {
1350
- const { userId } = config;
1351
- if (!userId) return;
1352
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1353
- setMessages(msgs);
1354
- setIsWaitingForResponse(isWaiting);
1355
- });
1356
- const active = activeStreamStore.get(userId);
1357
- if (active) {
1358
- setMessages(active.messages);
1359
- setIsWaitingForResponse(active.isWaiting);
1360
- }
1361
- return unsubscribe;
1362
- }, []);
1363
- react.useEffect(() => {
1364
- if (!config.userId) return;
1365
- const toSave = messages.filter((m) => !m.isStreaming);
1366
- if (toSave.length > 0) {
1367
- chatStore.set(config.userId, toSave);
1368
- }
1369
- }, [messages, config.userId]);
1370
- react.useEffect(() => {
1371
- const prevUserId = prevUserIdRef.current;
1372
- prevUserIdRef.current = config.userId;
1373
- if (prevUserId === config.userId) return;
1374
- if (prevUserId && !config.userId) {
1375
- chatStore.delete(prevUserId);
1376
- setMessages([]);
1377
- sessionIdRef.current = void 0;
1378
- setIsWaitingForResponse(false);
1379
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1380
- } else if (config.userId) {
1381
- const stored = chatStore.get(config.userId);
1382
- setMessages(stored);
1383
- sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
1384
- }
1385
- }, [config.userId]);
1386
- return {
1387
- messages,
1388
- sendMessage,
1389
- clearMessages,
1390
- prependMessages,
1391
- cancelStream,
1392
- resetSession,
1393
- getSessionId,
1394
- getMessages,
1395
- isWaitingForResponse,
1396
- sessionId: sessionIdRef.current,
1397
- // User action (OTP) state and methods
1398
- userActionState,
1399
- approveUserAction,
1400
- rejectUserAction,
1401
- resendOtp
1402
- };
395
+ });
396
+ if (!response.ok) {
397
+ const errorText = await response.text();
398
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
399
+ }
400
+ return await response.json();
401
+ }
402
+ async function submitUserAction(config, userActionId, data) {
403
+ return sendUserActionRequest(config, userActionId, "submit", data);
404
+ }
405
+ async function cancelUserAction(config, userActionId) {
406
+ return sendUserActionRequest(config, userActionId, "cancel");
1403
407
  }
408
+ async function resendUserAction(config, userActionId) {
409
+ return sendUserActionRequest(config, userActionId, "resend");
410
+ }
411
+ var storage = reactNativeMmkv.createMMKV({ id: "payman-chat-store" });
412
+ var chatStore = {
413
+ get(key) {
414
+ const raw = storage.getString(key);
415
+ if (!raw) return [];
416
+ try {
417
+ return JSON.parse(raw);
418
+ } catch {
419
+ return [];
420
+ }
421
+ },
422
+ set(key, messages) {
423
+ storage.set(key, JSON.stringify(messages));
424
+ },
425
+ delete(key) {
426
+ storage.delete(key);
427
+ }
428
+ };
429
+
430
+ // src/utils/activeStreamStore.ts
431
+ var streams = /* @__PURE__ */ new Map();
432
+ var activeStreamStore = {
433
+ has(key) {
434
+ return streams.has(key);
435
+ },
436
+ get(key) {
437
+ const entry = streams.get(key);
438
+ if (!entry) return null;
439
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
440
+ },
441
+ // Called before startStream — registers the controller and initial messages
442
+ start(key, abortController, initialMessages) {
443
+ const existing = streams.get(key);
444
+ streams.set(key, {
445
+ messages: initialMessages,
446
+ isWaiting: true,
447
+ abortController,
448
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
449
+ });
450
+ },
451
+ // Called by the stream on every event — applies the same updater pattern React uses
452
+ applyMessages(key, updater) {
453
+ const entry = streams.get(key);
454
+ if (!entry) return;
455
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
456
+ entry.messages = next;
457
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
458
+ },
459
+ setWaiting(key, waiting) {
460
+ const entry = streams.get(key);
461
+ if (!entry) return;
462
+ entry.isWaiting = waiting;
463
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
464
+ },
465
+ // Called when stream completes — persists to chatStore and cleans up
466
+ complete(key) {
467
+ const entry = streams.get(key);
468
+ if (!entry) return;
469
+ entry.isWaiting = false;
470
+ entry.listeners.forEach((l) => l(entry.messages, false));
471
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
472
+ if (toSave.length > 0) chatStore.set(key, toSave);
473
+ streams.delete(key);
474
+ },
475
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
476
+ subscribe(key, listener) {
477
+ const entry = streams.get(key);
478
+ if (!entry) return () => {
479
+ };
480
+ entry.listeners.add(listener);
481
+ return () => {
482
+ streams.get(key)?.listeners.delete(listener);
483
+ };
484
+ },
485
+ // Explicit user cancel — aborts the controller and removes the entry
486
+ abort(key) {
487
+ const entry = streams.get(key);
488
+ if (!entry) return;
489
+ entry.abortController.abort();
490
+ streams.delete(key);
491
+ }
492
+ };
1404
493
 
1405
494
  // src/utils/v2EventProcessor.ts
1406
495
  function getEventText(event, field) {
@@ -1426,7 +515,7 @@ function addThinkingLine(state, header, detail) {
1426
515
  function appendThinkingText(state, text) {
1427
516
  state.formattedThinkingText += text;
1428
517
  }
1429
- function completeLastInProgressStep2(steps) {
518
+ function completeLastInProgressStep(steps) {
1430
519
  for (let i = steps.length - 1; i >= 0; i--) {
1431
520
  if (steps[i].status === "in_progress") {
1432
521
  steps[i].status = "completed";
@@ -1523,7 +612,19 @@ function processStreamEventV2(event, state) {
1523
612
  break;
1524
613
  }
1525
614
  case "ORCHESTRATOR_COMPLETED": {
1526
- appendThinkingText(state, "\n" + (event.message || "Planning complete"));
615
+ const workingDetail = workingPhaseDetailForDisplay(message);
616
+ if (workingDetail) {
617
+ addThinkingLine(state, "**Working**", workingDetail);
618
+ } else {
619
+ addThinkingHeader(state, "**Working**");
620
+ }
621
+ state.steps.push({
622
+ id: `step-${state.stepCounter++}`,
623
+ eventType: "WORKING",
624
+ message: workingDetail,
625
+ status: "completed",
626
+ timestamp: Date.now()
627
+ });
1527
628
  const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
1528
629
  if (step) {
1529
630
  step.status = "completed";
@@ -1628,7 +729,7 @@ function processStreamEventV2(event, state) {
1628
729
  break;
1629
730
  }
1630
731
  case "USER_ACTION_REQUIRED": {
1631
- completeLastInProgressStep2(state.steps);
732
+ completeLastInProgressStep(state.steps);
1632
733
  if (event.userActionRequest) {
1633
734
  state.userActionRequest = {
1634
735
  userActionId: event.userActionRequest.userActionId,
@@ -1658,7 +759,7 @@ function processStreamEventV2(event, state) {
1658
759
  }
1659
760
  case "USER_ACTION_SUCCESS": {
1660
761
  appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
1661
- completeLastInProgressStep2(state.steps);
762
+ completeLastInProgressStep(state.steps);
1662
763
  state.userActionRequest = void 0;
1663
764
  state.userActionPending = false;
1664
765
  state.userActionResult = "approved";
@@ -1675,7 +776,7 @@ function processStreamEventV2(event, state) {
1675
776
  break;
1676
777
  }
1677
778
  case "USER_ACTION_INVALID": {
1678
- completeLastInProgressStep2(state.steps);
779
+ completeLastInProgressStep(state.steps);
1679
780
  const errorStepId = `step-${state.stepCounter++}`;
1680
781
  state.steps.push({
1681
782
  id: errorStepId,
@@ -1699,7 +800,7 @@ function processStreamEventV2(event, state) {
1699
800
  }
1700
801
  case "USER_ACTION_REJECTED": {
1701
802
  appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
1702
- completeLastInProgressStep2(state.steps);
803
+ completeLastInProgressStep(state.steps);
1703
804
  state.userActionRequest = void 0;
1704
805
  state.userActionPending = false;
1705
806
  state.userActionResult = "rejected";
@@ -1717,7 +818,7 @@ function processStreamEventV2(event, state) {
1717
818
  }
1718
819
  case "USER_ACTION_EXPIRED": {
1719
820
  appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
1720
- completeLastInProgressStep2(state.steps);
821
+ completeLastInProgressStep(state.steps);
1721
822
  state.userActionRequest = void 0;
1722
823
  state.userActionPending = false;
1723
824
  const stepId = `step-${state.stepCounter++}`;
@@ -1747,7 +848,7 @@ function processStreamEventV2(event, state) {
1747
848
  }
1748
849
  case "USER_ACTION_FAILED": {
1749
850
  appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
1750
- completeLastInProgressStep2(state.steps);
851
+ completeLastInProgressStep(state.steps);
1751
852
  state.userActionRequest = void 0;
1752
853
  state.userActionPending = false;
1753
854
  const stepId = `step-${state.stepCounter++}`;
@@ -1785,8 +886,58 @@ function processStreamEventV2(event, state) {
1785
886
  return state;
1786
887
  }
1787
888
 
889
+ // src/utils/ragImageResolver.ts
890
+ var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
891
+ function hasRagImages(content) {
892
+ return RAG_IMAGE_REGEX.test(content);
893
+ }
894
+ async function waitForNextPaint(signal) {
895
+ if (signal?.aborted) return;
896
+ await new Promise((resolve) => {
897
+ let isSettled = false;
898
+ const finish = () => {
899
+ if (isSettled) return;
900
+ isSettled = true;
901
+ signal?.removeEventListener("abort", finish);
902
+ resolve();
903
+ };
904
+ signal?.addEventListener("abort", finish, { once: true });
905
+ if (typeof requestAnimationFrame === "function") {
906
+ requestAnimationFrame(() => {
907
+ setTimeout(finish, 0);
908
+ });
909
+ return;
910
+ }
911
+ setTimeout(finish, 0);
912
+ });
913
+ }
914
+ async function resolveRagImageUrls(config, content, signal) {
915
+ const url = buildResolveImagesUrl(config);
916
+ const baseHeaders = buildRequestHeaders(config);
917
+ const headers = { "Content-Type": "application/json", ...baseHeaders };
918
+ const response = await fetch(url, {
919
+ method: "POST",
920
+ headers,
921
+ body: JSON.stringify({ input: content }),
922
+ signal
923
+ });
924
+ if (!response.ok) {
925
+ const errorText = await response.text();
926
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
927
+ }
928
+ const text = await response.text();
929
+ try {
930
+ const parsed = JSON.parse(text);
931
+ if (typeof parsed === "string") return parsed;
932
+ if (typeof parsed.output === "string") return parsed.output;
933
+ if (typeof parsed.result === "string") return parsed.result;
934
+ } catch {
935
+ }
936
+ return text;
937
+ }
938
+
1788
939
  // src/hooks/useStreamManagerV2.ts
1789
- var FRIENDLY_ERROR_MESSAGE2 = "Oops, something went wrong. Please try again.";
940
+ var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
1790
941
  function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
1791
942
  const abortControllerRef = react.useRef(null);
1792
943
  const configRef = react.useRef(config);
@@ -1834,8 +985,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1834
985
  const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
1835
986
  if (state.hasError) {
1836
987
  updateMessage({
1837
- streamingContent: FRIENDLY_ERROR_MESSAGE2,
1838
- content: FRIENDLY_ERROR_MESSAGE2,
988
+ streamingContent: FRIENDLY_ERROR_MESSAGE,
989
+ content: FRIENDLY_ERROR_MESSAGE,
1839
990
  streamProgress: "error",
1840
991
  isError: true,
1841
992
  errorDetails: state.errorMessage,
@@ -1885,7 +1036,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1885
1036
  isError: !isAborted,
1886
1037
  isCancelled: isAborted,
1887
1038
  errorDetails: isAborted ? void 0 : error.message,
1888
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1039
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1889
1040
  currentMessage: isAborted ? "Thinking..." : void 0,
1890
1041
  formattedThinkingText: state.formattedThinkingText || void 0,
1891
1042
  steps: [...state.steps].map((step) => {
@@ -1917,7 +1068,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1917
1068
  id: streamingId,
1918
1069
  sessionId: state.sessionId || sessionId,
1919
1070
  role: "assistant",
1920
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE2 : state.finalResponse || "",
1071
+ content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
1921
1072
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1922
1073
  isStreaming: false,
1923
1074
  streamProgress: state.hasError ? "error" : "completed",
@@ -1988,7 +1139,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1988
1139
  isError: !isAborted,
1989
1140
  isCancelled: isAborted,
1990
1141
  errorDetails: isAborted ? void 0 : error.message,
1991
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1142
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1992
1143
  formattedThinkingText: state.formattedThinkingText || void 0,
1993
1144
  steps: [...state.steps].map((step) => {
1994
1145
  if (step.status === "in_progress" && isAborted) {
@@ -2015,45 +1166,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
2015
1166
  };
2016
1167
  }
2017
1168
 
1169
+ // src/utils/workflowUsersClient.ts
1170
+ var DEFAULT_SESSION_PAGE_SIZE = 20;
1171
+ var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
1172
+ function getStageParamName2(config) {
1173
+ return config.api.stageQueryParam ?? "stage";
1174
+ }
1175
+ function requireOwnerId(config) {
1176
+ const ownerId = config.session?.owner?.id;
1177
+ if (!ownerId) {
1178
+ throw new Error(
1179
+ "workflowUsersClient: session.owner.id is required to call this endpoint."
1180
+ );
1181
+ }
1182
+ return ownerId;
1183
+ }
1184
+ function requireWorkflowName(config) {
1185
+ const workflowName = config.workflow.name;
1186
+ if (!workflowName) {
1187
+ throw new Error(
1188
+ "workflowUsersClient: workflow.name is required to call this endpoint."
1189
+ );
1190
+ }
1191
+ return workflowName;
1192
+ }
1193
+ function requireWorkflowId(config) {
1194
+ const workflowId = config.workflow.id;
1195
+ if (!workflowId) {
1196
+ throw new Error(
1197
+ "workflowUsersClient: workflow.id is required to call this endpoint."
1198
+ );
1199
+ }
1200
+ return workflowId;
1201
+ }
1202
+ function isPlaygroundYaakAuth(config) {
1203
+ if (config.api.authToken) return false;
1204
+ const headers = config.api.headers;
1205
+ if (!headers) return false;
1206
+ for (const key of Object.keys(headers)) {
1207
+ const lower = key.toLowerCase();
1208
+ if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
1209
+ }
1210
+ return false;
1211
+ }
1212
+ async function fetchJson(url, headers, signal) {
1213
+ const response = await fetch(url, {
1214
+ method: "GET",
1215
+ headers,
1216
+ signal
1217
+ });
1218
+ if (!response.ok) {
1219
+ const errorText = await response.text();
1220
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1221
+ }
1222
+ return await response.json();
1223
+ }
1224
+ async function listSessions(config, opts = {}) {
1225
+ const ownerId = requireOwnerId(config);
1226
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1227
+ const params = new URLSearchParams({
1228
+ page: String(opts.page ?? 0),
1229
+ size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
1230
+ });
1231
+ if (useYaakEndpoint) {
1232
+ params.set("workflowUserId", ownerId);
1233
+ params.set("workflowName", requireWorkflowName(config));
1234
+ } else {
1235
+ params.set("workflowId", requireWorkflowId(config));
1236
+ }
1237
+ if (config.workflow.stage) {
1238
+ params.set(getStageParamName2(config), config.workflow.stage);
1239
+ }
1240
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1241
+ ownerId
1242
+ )}/sessions?${params.toString()}`;
1243
+ return fetchJson(
1244
+ url,
1245
+ buildRequestHeaders(config),
1246
+ opts.signal
1247
+ );
1248
+ }
1249
+ async function listConversations(config, opts) {
1250
+ const ownerId = requireOwnerId(config);
1251
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1252
+ const params = new URLSearchParams({
1253
+ sessionId: opts.sessionId,
1254
+ page: String(opts.page ?? 0),
1255
+ size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
1256
+ });
1257
+ if (useYaakEndpoint) {
1258
+ params.set("workflowUserId", ownerId);
1259
+ params.set("workflowName", requireWorkflowName(config));
1260
+ }
1261
+ if (config.workflow.stage) {
1262
+ params.set(getStageParamName2(config), config.workflow.stage);
1263
+ }
1264
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1265
+ ownerId
1266
+ )}/conversations?${params.toString()}`;
1267
+ return fetchJson(
1268
+ url,
1269
+ buildRequestHeaders(config),
1270
+ opts.signal
1271
+ );
1272
+ }
1273
+
2018
1274
  // src/hooks/useChatV2.ts
1275
+ function conversationEntryToMessages(entry, sessionId) {
1276
+ return [
1277
+ {
1278
+ id: `history-user-${entry.executionId}`,
1279
+ sessionId,
1280
+ role: "user",
1281
+ content: entry.query,
1282
+ timestamp: entry.createdAt,
1283
+ isHistorical: true
1284
+ },
1285
+ {
1286
+ id: `history-assistant-${entry.executionId}`,
1287
+ sessionId,
1288
+ role: "assistant",
1289
+ content: entry.response,
1290
+ timestamp: entry.createdAt,
1291
+ executionId: entry.executionId,
1292
+ isHistorical: true
1293
+ }
1294
+ ];
1295
+ }
1296
+ function streamKeyFor(scopeKey, sessionId) {
1297
+ return `${scopeKey}|sid:${sessionId ?? ""}`;
1298
+ }
2019
1299
  function useChatV2(config, callbacks = {}) {
1300
+ const scopeKey = react.useMemo(() => buildScopeKey(config), [
1301
+ config.session?.userId,
1302
+ config.workflow.id,
1303
+ config.workflow.version,
1304
+ config.workflow.stage
1305
+ ]);
1306
+ const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
2020
1307
  const [messages, setMessages] = react.useState(() => {
2021
- if (config.userId) return chatStore.get(config.userId);
2022
- return config.initialMessages ?? [];
1308
+ const stored = chatStore.get(scopeKey);
1309
+ if (stored.length > 0) return stored;
1310
+ return config.session?.initialMessages ?? [];
2023
1311
  });
2024
1312
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
2025
- const sessionIdRef = react.useRef(
2026
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
2027
- );
2028
- const prevUserIdRef = react.useRef(config.userId);
1313
+ const [loadingSessionId, setLoadingSessionId] = react.useState(void 0);
1314
+ const [currentSessionId, setCurrentSessionId] = react.useState(initialSessionId);
1315
+ const sessionIdRef = react.useRef(initialSessionId);
1316
+ const prevScopeKeyRef = react.useRef(scopeKey);
1317
+ const activeStreamSessionRef = react.useRef(void 0);
2029
1318
  const callbacksRef = react.useRef(callbacks);
2030
1319
  callbacksRef.current = callbacks;
2031
1320
  const configRef = react.useRef(config);
2032
1321
  configRef.current = config;
1322
+ const scopeKeyRef = react.useRef(scopeKey);
1323
+ scopeKeyRef.current = scopeKey;
2033
1324
  const messagesRef = react.useRef(messages);
2034
1325
  messagesRef.current = messages;
2035
1326
  const storeAwareSetMessages = react.useCallback(
2036
1327
  (updater) => {
2037
- const { userId } = configRef.current;
2038
- if (userId && activeStreamStore.has(userId)) {
2039
- activeStreamStore.applyMessages(userId, updater);
1328
+ const scope = scopeKeyRef.current;
1329
+ const streamSid = activeStreamSessionRef.current;
1330
+ if (streamSid !== void 0) {
1331
+ const streamKey = streamKeyFor(scope, streamSid);
1332
+ if (activeStreamStore.has(streamKey)) {
1333
+ activeStreamStore.applyMessages(
1334
+ streamKey,
1335
+ updater
1336
+ );
1337
+ }
1338
+ if (sessionIdRef.current === streamSid) {
1339
+ setMessages(updater);
1340
+ }
1341
+ return;
2040
1342
  }
2041
1343
  setMessages(updater);
2042
1344
  },
2043
- // eslint-disable-next-line react-hooks/exhaustive-deps
2044
1345
  []
2045
1346
  );
2046
- const storeAwareSetIsWaiting = react.useCallback(
2047
- (waiting) => {
2048
- const { userId } = configRef.current;
2049
- if (userId && activeStreamStore.has(userId)) {
2050
- activeStreamStore.setWaiting(userId, waiting);
1347
+ const storeAwareSetIsWaiting = react.useCallback((waiting) => {
1348
+ const scope = scopeKeyRef.current;
1349
+ const streamSid = activeStreamSessionRef.current;
1350
+ if (streamSid !== void 0) {
1351
+ const streamKey = streamKeyFor(scope, streamSid);
1352
+ if (activeStreamStore.has(streamKey)) {
1353
+ activeStreamStore.setWaiting(streamKey, waiting);
2051
1354
  }
2052
- setIsWaitingForResponse(waiting);
2053
- },
2054
- // eslint-disable-next-line react-hooks/exhaustive-deps
2055
- []
2056
- );
1355
+ if (sessionIdRef.current === streamSid) {
1356
+ setIsWaitingForResponse(waiting);
1357
+ }
1358
+ return;
1359
+ }
1360
+ setIsWaitingForResponse(waiting);
1361
+ }, []);
2057
1362
  const [userActionState, setUserActionState] = react.useState({
2058
1363
  request: null,
2059
1364
  result: null,
@@ -2061,38 +1366,43 @@ function useChatV2(config, callbacks = {}) {
2061
1366
  });
2062
1367
  const userActionStateRef = react.useRef(userActionState);
2063
1368
  userActionStateRef.current = userActionState;
2064
- const wrappedCallbacks = react.useMemo(() => ({
2065
- ...callbacksRef.current,
2066
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
2067
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
2068
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
2069
- onError: (error) => callbacksRef.current.onError?.(error),
2070
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
2071
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
2072
- onUserActionRequired: (request) => {
2073
- setUserActionState((prev) => ({ ...prev, request, result: null }));
2074
- callbacksRef.current.onUserActionRequired?.(request);
2075
- },
2076
- onUserActionEvent: (eventType, message) => {
2077
- switch (eventType) {
2078
- case "USER_ACTION_SUCCESS":
2079
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
2080
- break;
2081
- case "USER_ACTION_REJECTED":
2082
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
2083
- break;
2084
- case "USER_ACTION_EXPIRED":
2085
- case "USER_ACTION_FAILED":
2086
- setUserActionState((prev) => ({ ...prev, request: null }));
2087
- break;
2088
- case "USER_ACTION_INVALID":
2089
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
2090
- break;
1369
+ const wrappedCallbacks = react.useMemo(
1370
+ () => ({
1371
+ ...callbacksRef.current,
1372
+ onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1373
+ onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1374
+ onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1375
+ onError: (error) => callbacksRef.current.onError?.(error),
1376
+ onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1377
+ onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1378
+ onUserActionRequired: (request) => {
1379
+ setUserActionState((prev) => ({ ...prev, request, result: null }));
1380
+ callbacksRef.current.onUserActionRequired?.(request);
1381
+ },
1382
+ onUserActionEvent: (eventType, message) => {
1383
+ switch (eventType) {
1384
+ case "USER_ACTION_SUCCESS":
1385
+ setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1386
+ break;
1387
+ case "USER_ACTION_REJECTED":
1388
+ setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1389
+ break;
1390
+ case "USER_ACTION_EXPIRED":
1391
+ case "USER_ACTION_FAILED":
1392
+ setUserActionState((prev) => ({ ...prev, request: null }));
1393
+ break;
1394
+ case "USER_ACTION_INVALID":
1395
+ setUserActionState((prev) => ({
1396
+ ...prev,
1397
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1398
+ }));
1399
+ break;
1400
+ }
1401
+ callbacksRef.current.onUserActionEvent?.(eventType, message);
2091
1402
  }
2092
- callbacksRef.current.onUserActionEvent?.(eventType, message);
2093
- }
2094
- // eslint-disable-next-line react-hooks/exhaustive-deps
2095
- }), []);
1403
+ }),
1404
+ []
1405
+ );
2096
1406
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
2097
1407
  config,
2098
1408
  wrappedCallbacks,
@@ -2102,8 +1412,10 @@ function useChatV2(config, callbacks = {}) {
2102
1412
  const sendMessage = react.useCallback(
2103
1413
  async (userMessage) => {
2104
1414
  if (!userMessage.trim()) return;
2105
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1415
+ const autoGen = configRef.current.session?.autoGenerateId;
1416
+ if (!sessionIdRef.current && autoGen !== false) {
2106
1417
  sessionIdRef.current = generateId();
1418
+ setCurrentSessionId(sessionIdRef.current);
2107
1419
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
2108
1420
  }
2109
1421
  const userMessageId = `user-${Date.now()}`;
@@ -2135,38 +1447,40 @@ function useChatV2(config, callbacks = {}) {
2135
1447
  };
2136
1448
  setMessages((prev) => [...prev, streamingMsg]);
2137
1449
  const abortController = new AbortController();
2138
- const { userId } = configRef.current;
2139
- if (userId) {
2140
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
2141
- activeStreamStore.start(userId, abortController, initialMessages);
2142
- }
1450
+ const scope = scopeKeyRef.current;
1451
+ const streamSessionId = sessionIdRef.current;
1452
+ const streamKey = streamKeyFor(scope, streamSessionId);
1453
+ activeStreamSessionRef.current = streamSessionId;
1454
+ const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1455
+ activeStreamStore.start(streamKey, abortController, initialMessages);
2143
1456
  const newSessionId = await startStream(
2144
1457
  userMessage,
2145
1458
  streamingId,
2146
- sessionIdRef.current,
1459
+ streamSessionId,
2147
1460
  abortController
2148
1461
  );
2149
- if (userId) {
2150
- activeStreamStore.complete(userId);
2151
- }
2152
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1462
+ activeStreamStore.complete(streamKey);
1463
+ activeStreamSessionRef.current = void 0;
1464
+ if (!abortController.signal.aborted && newSessionId && newSessionId !== streamSessionId && sessionIdRef.current === streamSessionId) {
2153
1465
  sessionIdRef.current = newSessionId;
1466
+ setCurrentSessionId(newSessionId);
2154
1467
  }
2155
1468
  },
2156
1469
  [startStream]
2157
1470
  );
2158
1471
  const clearMessages = react.useCallback(() => {
2159
- if (configRef.current.userId) {
2160
- chatStore.delete(configRef.current.userId);
2161
- }
1472
+ chatStore.delete(scopeKeyRef.current);
2162
1473
  setMessages([]);
2163
1474
  }, []);
2164
1475
  const prependMessages = react.useCallback((msgs) => {
2165
1476
  setMessages((prev) => [...msgs, ...prev]);
2166
1477
  }, []);
2167
1478
  const cancelStream = react.useCallback(() => {
2168
- if (configRef.current.userId) {
2169
- activeStreamStore.abort(configRef.current.userId);
1479
+ const scope = scopeKeyRef.current;
1480
+ const viewSid = sessionIdRef.current;
1481
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1482
+ if (activeStreamStore.has(viewStreamKey)) {
1483
+ activeStreamStore.abort(viewStreamKey);
2170
1484
  }
2171
1485
  cancelStreamManager();
2172
1486
  setIsWaitingForResponse(false);
@@ -2176,10 +1490,7 @@ function useChatV2(config, callbacks = {}) {
2176
1490
  if (msg.isStreaming) {
2177
1491
  return {
2178
1492
  ...msg,
2179
- ...createCancelledMessageUpdate(
2180
- msg.steps || [],
2181
- msg.currentMessage
2182
- )
1493
+ ...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
2183
1494
  };
2184
1495
  }
2185
1496
  return msg;
@@ -2187,39 +1498,37 @@ function useChatV2(config, callbacks = {}) {
2187
1498
  );
2188
1499
  }, [cancelStreamManager]);
2189
1500
  const resetSession = react.useCallback(() => {
2190
- if (configRef.current.userId) {
2191
- activeStreamStore.abort(configRef.current.userId);
2192
- chatStore.delete(configRef.current.userId);
1501
+ const scope = scopeKeyRef.current;
1502
+ const viewSid = sessionIdRef.current;
1503
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1504
+ if (activeStreamStore.has(viewStreamKey)) {
1505
+ activeStreamStore.abort(viewStreamKey);
2193
1506
  }
1507
+ chatStore.delete(scope);
2194
1508
  setMessages([]);
2195
1509
  sessionIdRef.current = void 0;
1510
+ setCurrentSessionId(void 0);
1511
+ activeStreamSessionRef.current = void 0;
2196
1512
  abortControllerRef.current?.abort();
2197
1513
  setIsWaitingForResponse(false);
2198
1514
  setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2199
1515
  }, []);
2200
- const getSessionId = react.useCallback(() => {
2201
- return sessionIdRef.current;
1516
+ const getSessionId = react.useCallback(() => sessionIdRef.current, []);
1517
+ const getMessages = react.useCallback(() => messages, [messages]);
1518
+ const approveUserAction = react.useCallback(async (otp) => {
1519
+ const request = userActionStateRef.current.request;
1520
+ if (!request) return;
1521
+ try {
1522
+ await submitUserAction(configRef.current, request.userActionId, { otp });
1523
+ } catch (error) {
1524
+ setUserActionState((prev) => ({
1525
+ ...prev,
1526
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1527
+ }));
1528
+ callbacksRef.current.onError?.(error);
1529
+ throw error;
1530
+ }
2202
1531
  }, []);
2203
- const getMessages = react.useCallback(() => {
2204
- return messages;
2205
- }, [messages]);
2206
- const approveUserAction = react.useCallback(
2207
- async (otp) => {
2208
- const request = userActionStateRef.current.request;
2209
- if (!request) return;
2210
- try {
2211
- await submitUserAction(configRef.current, request.userActionId, { otp });
2212
- } catch (error) {
2213
- setUserActionState((prev) => ({
2214
- ...prev,
2215
- clearOtpTrigger: prev.clearOtpTrigger + 1
2216
- }));
2217
- callbacksRef.current.onError?.(error);
2218
- throw error;
2219
- }
2220
- },
2221
- []
2222
- );
2223
1532
  const rejectUserAction = react.useCallback(async () => {
2224
1533
  const request = userActionStateRef.current.request;
2225
1534
  if (!request) return;
@@ -2253,43 +1562,91 @@ function useChatV2(config, callbacks = {}) {
2253
1562
  throw error;
2254
1563
  }
2255
1564
  }, []);
1565
+ const inFlightLoadRef = react.useRef(null);
1566
+ const loadingSessionIdRef = react.useRef(void 0);
1567
+ loadingSessionIdRef.current = loadingSessionId;
1568
+ const loadSession = react.useCallback(async (sessionId) => {
1569
+ const inFlight = inFlightLoadRef.current;
1570
+ if (inFlight && inFlight.sessionId === sessionId) {
1571
+ return inFlight.promise;
1572
+ }
1573
+ if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
1574
+ return;
1575
+ }
1576
+ const run = async () => {
1577
+ const scope = scopeKeyRef.current;
1578
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1579
+ sessionIdRef.current = sessionId;
1580
+ setCurrentSessionId(sessionId);
1581
+ callbacksRef.current.onSessionIdChange?.(sessionId);
1582
+ const streamKey = streamKeyFor(scope, sessionId);
1583
+ const active = activeStreamStore.get(streamKey);
1584
+ if (active) {
1585
+ setMessages(active.messages);
1586
+ setIsWaitingForResponse(active.isWaiting);
1587
+ setLoadingSessionId(void 0);
1588
+ return;
1589
+ }
1590
+ setIsWaitingForResponse(false);
1591
+ setMessages([]);
1592
+ chatStore.delete(scope);
1593
+ setLoadingSessionId(sessionId);
1594
+ try {
1595
+ const response = await listConversations(configRef.current, { sessionId });
1596
+ const entries = response.data ?? [];
1597
+ const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
1598
+ if (sessionIdRef.current === sessionId) {
1599
+ setMessages(historical);
1600
+ }
1601
+ if (historical.length > 0) {
1602
+ chatStore.set(scope, historical);
1603
+ }
1604
+ } catch (error) {
1605
+ callbacksRef.current.onError?.(error);
1606
+ throw error;
1607
+ } finally {
1608
+ setLoadingSessionId((current) => current === sessionId ? void 0 : current);
1609
+ }
1610
+ };
1611
+ const promise = run().finally(() => {
1612
+ if (inFlightLoadRef.current?.sessionId === sessionId) {
1613
+ inFlightLoadRef.current = null;
1614
+ }
1615
+ });
1616
+ inFlightLoadRef.current = { sessionId, promise };
1617
+ return promise;
1618
+ }, []);
2256
1619
  react.useEffect(() => {
2257
- const { userId } = config;
2258
- if (!userId) return;
2259
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1620
+ const key = streamKeyFor(scopeKey, currentSessionId);
1621
+ const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
2260
1622
  setMessages(msgs);
2261
1623
  setIsWaitingForResponse(isWaiting);
2262
1624
  });
2263
- const active = activeStreamStore.get(userId);
1625
+ const active = activeStreamStore.get(key);
2264
1626
  if (active) {
2265
1627
  setMessages(active.messages);
2266
1628
  setIsWaitingForResponse(active.isWaiting);
2267
1629
  }
2268
1630
  return unsubscribe;
2269
- }, []);
1631
+ }, [scopeKey, currentSessionId]);
2270
1632
  react.useEffect(() => {
2271
- if (!config.userId) return;
2272
1633
  const toSave = messages.filter((m) => !m.isStreaming);
2273
1634
  if (toSave.length > 0) {
2274
- chatStore.set(config.userId, toSave);
1635
+ chatStore.set(scopeKey, toSave);
2275
1636
  }
2276
- }, [messages, config.userId]);
1637
+ }, [messages, scopeKey]);
2277
1638
  react.useEffect(() => {
2278
- const prevUserId = prevUserIdRef.current;
2279
- prevUserIdRef.current = config.userId;
2280
- if (prevUserId === config.userId) return;
2281
- if (prevUserId && !config.userId) {
2282
- chatStore.delete(prevUserId);
2283
- setMessages([]);
2284
- sessionIdRef.current = void 0;
2285
- setIsWaitingForResponse(false);
2286
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2287
- } else if (config.userId) {
2288
- const stored = chatStore.get(config.userId);
2289
- setMessages(stored);
2290
- sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
2291
- }
2292
- }, [config.userId]);
1639
+ const prevKey = prevScopeKeyRef.current;
1640
+ prevScopeKeyRef.current = scopeKey;
1641
+ if (prevKey === scopeKey) return;
1642
+ const stored = chatStore.get(scopeKey);
1643
+ setMessages(stored);
1644
+ const restoredSessionId = stored.find((m) => m.sessionId)?.sessionId ?? configRef.current.session?.initialId ?? void 0;
1645
+ sessionIdRef.current = restoredSessionId;
1646
+ setCurrentSessionId(restoredSessionId);
1647
+ setIsWaitingForResponse(false);
1648
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1649
+ }, [scopeKey]);
2293
1650
  return {
2294
1651
  messages,
2295
1652
  sendMessage,
@@ -2300,11 +1657,13 @@ function useChatV2(config, callbacks = {}) {
2300
1657
  getSessionId,
2301
1658
  getMessages,
2302
1659
  isWaitingForResponse,
2303
- sessionId: sessionIdRef.current,
1660
+ sessionId: currentSessionId,
2304
1661
  userActionState,
2305
1662
  approveUserAction,
2306
1663
  rejectUserAction,
2307
- resendOtp
1664
+ resendOtp,
1665
+ loadSession,
1666
+ loadingSessionId
2308
1667
  };
2309
1668
  }
2310
1669
  function useVoice() {
@@ -2424,15 +1783,18 @@ function useVoice() {
2424
1783
  }
2425
1784
 
2426
1785
  exports.buildFormattedThinking = buildFormattedThinking;
1786
+ exports.buildScopeKey = buildScopeKey;
2427
1787
  exports.cancelUserAction = cancelUserAction;
2428
1788
  exports.createInitialV2State = createInitialV2State;
2429
1789
  exports.generateId = generateId;
1790
+ exports.listConversations = listConversations;
1791
+ exports.listSessions = listSessions;
2430
1792
  exports.processStreamEventV2 = processStreamEventV2;
2431
1793
  exports.resendUserAction = resendUserAction;
2432
1794
  exports.streamWorkflowEvents = streamWorkflowEvents;
2433
1795
  exports.submitUserAction = submitUserAction;
2434
- exports.useChat = useChat;
2435
1796
  exports.useChatV2 = useChatV2;
2436
1797
  exports.useVoice = useVoice;
1798
+ exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
2437
1799
  //# sourceMappingURL=index.native.js.map
2438
1800
  //# sourceMappingURL=index.native.js.map