@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.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
 
5
- // src/hooks/useChat.ts
5
+ // src/hooks/useChatV2.ts
6
6
 
7
7
  // src/utils/generateId.ts
8
8
  function generateId() {
@@ -168,6 +168,14 @@ function getEventMessage(event) {
168
168
  return eventType;
169
169
  }
170
170
  }
171
+ function workingPhaseDetailForDisplay(raw) {
172
+ const t = raw.trim();
173
+ if (!t) return "";
174
+ if (/^Identified\s+\d+\s+tasks?\s+to\s+execute\.?$/i.test(t)) {
175
+ return "";
176
+ }
177
+ return t;
178
+ }
171
179
  function extractResponseContent(response) {
172
180
  if (typeof response === "string") {
173
181
  return response;
@@ -190,290 +198,8 @@ function extractResponseContent(response) {
190
198
  }
191
199
  return "";
192
200
  }
193
- function completeLastInProgressStep(steps) {
194
- for (let i = steps.length - 1; i >= 0; i--) {
195
- if (steps[i].status === "in_progress") {
196
- steps[i].status = "completed";
197
- return;
198
- }
199
- }
200
- }
201
- function processStreamEvent(event, state) {
202
- const eventType = event.eventType;
203
- if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
204
- return state;
205
- }
206
- const message = getEventMessage(event);
207
- if (eventType !== "INTENT_THINKING" && eventType !== "INTENT_THINKING_CONT") {
208
- if (state.currentThinkingStepId) {
209
- const thinkingStep = state.steps.find((s) => s.id === state.currentThinkingStepId);
210
- if (thinkingStep) {
211
- thinkingStep.isThinking = false;
212
- }
213
- state.currentThinkingStepId = void 0;
214
- }
215
- state.activeThinkingText = void 0;
216
- }
217
- if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
218
- let content = extractResponseContent(event.response);
219
- const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
220
- if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
221
- content = trace.workflowMsg;
222
- }
223
- if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
224
- const agg = trace.aggregator;
225
- if (typeof agg.response === "string") content = agg.response;
226
- else content = extractResponseContent(agg.response);
227
- }
228
- if (content) {
229
- state.accumulatedContent = content;
230
- state.finalData = event.response ?? event.trace;
231
- state.hasError = false;
232
- state.errorMessage = "";
233
- } else {
234
- state.hasError = true;
235
- state.errorMessage = "WORKFLOW_FAILED";
236
- }
237
- }
238
- if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
239
- state.steps.forEach((step) => {
240
- if (step.status === "in_progress") {
241
- step.status = "completed";
242
- }
243
- });
244
- } else if (eventType === "INTENT_ERROR") {
245
- state.errorMessage = message || event.errorMessage || "An error occurred";
246
- const intentStep = state.steps.find(
247
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
248
- );
249
- if (intentStep) {
250
- intentStep.status = "error";
251
- }
252
- } else if (eventType === "ERROR" || eventType === "WORKFLOW_ERROR") {
253
- state.hasError = true;
254
- state.errorMessage = message || event.errorMessage || "An error occurred";
255
- } else if (eventType === "ORCHESTRATOR_COMPLETED") {
256
- state.inOrchestratorPhase = false;
257
- const orchestratorStep = state.steps.find(
258
- (s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress"
259
- );
260
- if (orchestratorStep) {
261
- orchestratorStep.status = "completed";
262
- if (event.elapsedMs) {
263
- orchestratorStep.elapsedMs = event.elapsedMs;
264
- }
265
- if (orchestratorStep.id === state.currentExecutingStepId) {
266
- state.currentExecutingStepId = void 0;
267
- }
268
- }
269
- } else if (eventType === "INTENT_COMPLETED") {
270
- const intentStep = state.steps.find(
271
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
272
- );
273
- if (intentStep) {
274
- intentStep.status = "completed";
275
- if (event.elapsedMs) {
276
- intentStep.elapsedMs = event.elapsedMs;
277
- }
278
- if (intentStep.id === state.currentExecutingStepId) {
279
- state.currentExecutingStepId = void 0;
280
- }
281
- }
282
- } else if (eventType === "AGGREGATOR_COMPLETED") {
283
- state.inAggregatorPhase = false;
284
- const aggregatorStep = state.steps.find(
285
- (s) => s.eventType === "AGGREGATOR_THINKING" && s.status === "in_progress"
286
- );
287
- if (aggregatorStep) {
288
- aggregatorStep.status = "completed";
289
- if (event.elapsedMs) {
290
- aggregatorStep.elapsedMs = event.elapsedMs;
291
- }
292
- if (aggregatorStep.id === state.currentExecutingStepId) {
293
- state.currentExecutingStepId = void 0;
294
- }
295
- }
296
- } else if (eventType === "ORCHESTRATOR_THINKING" || eventType === "INTENT_STARTED" || eventType === "INTENT_PROGRESS" || eventType === "AGGREGATOR_THINKING") {
297
- if (eventType === "ORCHESTRATOR_THINKING") {
298
- state.inOrchestratorPhase = true;
299
- }
300
- if (eventType === "AGGREGATOR_THINKING") {
301
- state.inAggregatorPhase = true;
302
- }
303
- if (eventType === "INTENT_PROGRESS") {
304
- const intentStep = state.steps.find(
305
- (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
306
- );
307
- if (intentStep) {
308
- intentStep.message = message;
309
- state.currentExecutingStepId = intentStep.id;
310
- } else {
311
- const stepId = `step-${state.stepCounter++}`;
312
- state.steps.push({
313
- id: stepId,
314
- eventType: "INTENT_STARTED",
315
- message,
316
- status: "in_progress",
317
- timestamp: Date.now(),
318
- elapsedMs: event.elapsedMs
319
- });
320
- state.currentExecutingStepId = stepId;
321
- }
322
- } else {
323
- const stepId = `step-${state.stepCounter++}`;
324
- state.steps.push({
325
- id: stepId,
326
- eventType,
327
- message,
328
- status: "in_progress",
329
- timestamp: Date.now(),
330
- elapsedMs: event.elapsedMs
331
- });
332
- state.currentExecutingStepId = stepId;
333
- }
334
- } else if (eventType === "USER_ACTION_REQUIRED") {
335
- completeLastInProgressStep(state.steps);
336
- if (event.userActionRequest) {
337
- state.userActionRequest = {
338
- userActionId: event.userActionRequest.userActionId,
339
- userActionType: event.userActionRequest.userActionType,
340
- message: event.userActionRequest.message,
341
- requestedSchema: event.userActionRequest.requestedSchema,
342
- metadata: event.userActionRequest.metadata
343
- };
344
- }
345
- state.userActionPending = true;
346
- const stepId = `step-${state.stepCounter++}`;
347
- state.steps.push({
348
- id: stepId,
349
- eventType,
350
- message,
351
- status: "in_progress",
352
- timestamp: Date.now(),
353
- elapsedMs: event.elapsedMs
354
- });
355
- state.currentExecutingStepId = stepId;
356
- } else if (eventType === "USER_ACTION_SUCCESS") {
357
- completeLastInProgressStep(state.steps);
358
- state.userActionRequest = void 0;
359
- state.userActionPending = false;
360
- state.userActionResult = "approved";
361
- const stepId = `step-${state.stepCounter++}`;
362
- state.steps.push({
363
- id: stepId,
364
- eventType,
365
- message,
366
- status: "completed",
367
- timestamp: Date.now(),
368
- elapsedMs: event.elapsedMs
369
- });
370
- } else if (eventType === "USER_ACTION_INVALID") {
371
- completeLastInProgressStep(state.steps);
372
- const errorStepId = `step-${state.stepCounter++}`;
373
- state.steps.push({
374
- id: errorStepId,
375
- eventType,
376
- message,
377
- status: "error",
378
- timestamp: Date.now(),
379
- elapsedMs: event.elapsedMs
380
- });
381
- const retryStepId = `step-${state.stepCounter++}`;
382
- state.steps.push({
383
- id: retryStepId,
384
- eventType: "USER_ACTION_REQUIRED",
385
- message: "Waiting for verification...",
386
- status: "in_progress",
387
- timestamp: Date.now()
388
- });
389
- state.currentExecutingStepId = retryStepId;
390
- } else if (eventType === "USER_ACTION_EXPIRED") {
391
- completeLastInProgressStep(state.steps);
392
- state.userActionRequest = void 0;
393
- state.userActionPending = false;
394
- const stepId = `step-${state.stepCounter++}`;
395
- state.steps.push({
396
- id: stepId,
397
- eventType,
398
- message,
399
- status: "error",
400
- timestamp: Date.now(),
401
- elapsedMs: event.elapsedMs
402
- });
403
- } else if (eventType === "USER_ACTION_REJECTED") {
404
- completeLastInProgressStep(state.steps);
405
- state.userActionRequest = void 0;
406
- state.userActionPending = false;
407
- state.userActionResult = "rejected";
408
- const stepId = `step-${state.stepCounter++}`;
409
- state.steps.push({
410
- id: stepId,
411
- eventType,
412
- message,
413
- status: "completed",
414
- timestamp: Date.now(),
415
- elapsedMs: event.elapsedMs
416
- });
417
- } else if (eventType === "USER_ACTION_RESENT") {
418
- const stepId = `step-${state.stepCounter++}`;
419
- state.steps.push({
420
- id: stepId,
421
- eventType,
422
- message,
423
- status: "completed",
424
- timestamp: Date.now(),
425
- elapsedMs: event.elapsedMs
426
- });
427
- } else if (eventType === "USER_ACTION_FAILED") {
428
- completeLastInProgressStep(state.steps);
429
- state.userActionRequest = void 0;
430
- state.userActionPending = false;
431
- const stepId = `step-${state.stepCounter++}`;
432
- state.steps.push({
433
- id: stepId,
434
- eventType,
435
- message,
436
- status: "error",
437
- timestamp: Date.now(),
438
- elapsedMs: event.elapsedMs
439
- });
440
- } else if (eventType === "INTENT_THINKING") {
441
- if (state.currentThinkingStepId) {
442
- const prev = state.steps.find((s) => s.id === state.currentThinkingStepId);
443
- if (prev) prev.isThinking = false;
444
- }
445
- const lastInProgress = [...state.steps].reverse().find((s) => s.status === "in_progress");
446
- if (lastInProgress) {
447
- lastInProgress.thinkingText = "";
448
- lastInProgress.isThinking = true;
449
- state.currentThinkingStepId = lastInProgress.id;
450
- } else {
451
- state.currentThinkingStepId = void 0;
452
- }
453
- if (state.allThinkingText) state.allThinkingText += "\n\n";
454
- if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
455
- state.activeThinkingText = "";
456
- }
457
- } else if (eventType === "INTENT_THINKING_CONT") {
458
- const delta = event.message || "";
459
- if (!delta) return state;
460
- if (state.currentThinkingStepId) {
461
- const step = state.steps.find((s) => s.id === state.currentThinkingStepId);
462
- if (step) {
463
- step.thinkingText = (step.thinkingText || "") + delta;
464
- }
465
- }
466
- state.allThinkingText += delta;
467
- if (!state.inOrchestratorPhase && !state.inAggregatorPhase) {
468
- if (state.activeThinkingText == null) state.activeThinkingText = "";
469
- state.activeThinkingText += delta;
470
- }
471
- }
472
- return state;
473
- }
474
201
 
475
202
  // src/utils/messageStateManager.ts
476
- var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
477
203
  function buildFormattedThinking(steps, allThinkingText) {
478
204
  const parts = [];
479
205
  const safeSteps = steps ?? [];
@@ -503,6 +229,12 @@ function buildFormattedThinking(steps, allThinkingText) {
503
229
  parts.push("**Planning**");
504
230
  if (step.message) parts.push(step.message);
505
231
  break;
232
+ case "WORKING": {
233
+ const detail = workingPhaseDetailForDisplay(step.message || "");
234
+ parts.push("**Working**");
235
+ if (detail) parts.push(detail);
236
+ break;
237
+ }
506
238
  case "INTENT_STARTED": {
507
239
  let label = step.message || "Processing";
508
240
  const started = label.match(/^(.+?)\s+started$/i);
@@ -542,73 +274,6 @@ function buildFormattedThinking(steps, allThinkingText) {
542
274
  }
543
275
  return parts.length > 0 ? parts.join("\n") : allThinkingText;
544
276
  }
545
- function createStreamingMessageUpdate(state) {
546
- const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
547
- const steps = state.hasError ? [] : [...state.steps];
548
- const allThinking = state.hasError ? void 0 : state.allThinkingText;
549
- return {
550
- streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
551
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
552
- currentMessage: state.hasError ? void 0 : state.currentMessage,
553
- streamProgress: state.hasError ? "error" : "processing",
554
- isError: state.hasError,
555
- errorDetails: state.hasError ? state.errorMessage : void 0,
556
- executionId: state.executionId,
557
- sessionId: state.sessionId,
558
- steps,
559
- currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
560
- isCancelled: false,
561
- userActionResult: state.userActionResult,
562
- activeThinkingText: state.hasError ? void 0 : state.activeThinkingText,
563
- allThinkingText: allThinking,
564
- formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || "")
565
- };
566
- }
567
- function createErrorMessageUpdate(error, state) {
568
- const isAborted = error.name === "AbortError";
569
- return {
570
- isStreaming: false,
571
- streamProgress: isAborted ? "processing" : "error",
572
- isError: !isAborted,
573
- isCancelled: isAborted,
574
- errorDetails: isAborted ? void 0 : error.message,
575
- content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
576
- // Preserve currentMessage when cancelled so UI can show it
577
- currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
578
- steps: [...state.steps].map((step) => {
579
- if (step.status === "in_progress" && isAborted) {
580
- return { ...step, status: "pending" };
581
- }
582
- return step;
583
- }),
584
- currentExecutingStepId: void 0
585
- };
586
- }
587
- function createFinalMessage(streamingId, state) {
588
- const steps = state.hasError ? [] : [...state.steps];
589
- const allThinking = state.hasError ? void 0 : state.allThinkingText;
590
- return {
591
- id: streamingId,
592
- sessionId: state.sessionId,
593
- role: "assistant",
594
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
595
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
596
- isStreaming: false,
597
- streamProgress: state.hasError ? "error" : "completed",
598
- isError: state.hasError,
599
- errorDetails: state.hasError ? state.errorMessage : void 0,
600
- executionId: state.executionId,
601
- tracingData: state.finalData,
602
- steps,
603
- isCancelled: false,
604
- currentExecutingStepId: void 0,
605
- userActionResult: state.userActionResult,
606
- activeThinkingText: void 0,
607
- allThinkingText: allThinking,
608
- formattedThinkingText: state.hasError ? void 0 : buildFormattedThinking(steps, allThinking || ""),
609
- isResolvingImages: state.hasError ? void 0 : state.isResolvingImages
610
- };
611
- }
612
277
  function createCancelledMessageUpdate(steps, currentMessage) {
613
278
  const updatedSteps = steps.map((step) => {
614
279
  if (step.status === "in_progress") {
@@ -628,35 +293,44 @@ function createCancelledMessageUpdate(steps, currentMessage) {
628
293
 
629
294
  // src/utils/requestBuilder.ts
630
295
  function buildRequestBody(config, userMessage, sessionId) {
631
- const sessionOwner = config.sessionParams;
632
- const sessionAttributes = sessionOwner?.attributes && Object.keys(sessionOwner.attributes).length > 0 ? sessionOwner.attributes : void 0;
296
+ const owner = config.session?.owner;
297
+ const sessionAttributes = owner?.attributes && Object.keys(owner.attributes).length > 0 ? owner.attributes : void 0;
633
298
  return {
634
- workflowName: config.workflowName,
299
+ workflowName: config.workflow.name,
635
300
  userInput: userMessage,
636
301
  sessionId,
637
- sessionOwnerId: sessionOwner?.id || "",
638
- sessionOwnerLabel: sessionOwner?.name || "",
302
+ sessionOwnerId: owner?.id || "",
303
+ sessionOwnerLabel: owner?.name || "",
639
304
  sessionAttributes,
640
305
  options: {
641
306
  clientTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
642
307
  }
643
308
  };
644
309
  }
310
+ function getStageParamName(config) {
311
+ return config.api.stageQueryParam ?? "stage";
312
+ }
313
+ function getStage(config) {
314
+ return config.workflow.stage ?? "DEV";
315
+ }
645
316
  function buildStreamingUrl(config) {
646
317
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
647
- const stage = config.stage || "DEV";
648
- const stageParamName = config.stageQueryParam ?? "stage";
649
- const queryParams = new URLSearchParams({ [stageParamName]: stage });
650
- if (config.workflowVersion !== void 0) {
651
- queryParams.append("workflowVersion", String(config.workflowVersion));
318
+ const queryParams = new URLSearchParams({
319
+ [getStageParamName(config)]: getStage(config)
320
+ });
321
+ if (config.workflow.version !== void 0) {
322
+ queryParams.append("workflowVersion", String(config.workflow.version));
652
323
  }
653
324
  return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
654
325
  }
655
- function buildUserActionUrl(config, userActionId, action) {
326
+ function deriveBasePath(config) {
656
327
  const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
657
328
  const [endpointPath] = endpoint.split("?");
658
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
659
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
329
+ const normalized = endpointPath.replace(/\/+$/, "");
330
+ return normalized.endsWith("/stream") ? normalized.slice(0, -"/stream".length) : normalized;
331
+ }
332
+ function buildUserActionUrl(config, userActionId, action) {
333
+ const basePath = deriveBasePath(config);
660
334
  const encodedUserActionId = encodeURIComponent(userActionId);
661
335
  return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
662
336
  }
@@ -664,21 +338,23 @@ function buildResolveImagesUrl(config) {
664
338
  if (config.api.resolveImagesEndpoint) {
665
339
  return `${config.api.baseUrl}${config.api.resolveImagesEndpoint}`;
666
340
  }
667
- const streamEndpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
668
- const [endpointPath] = streamEndpoint.split("?");
669
- const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
670
- const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
671
- return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
341
+ return `${config.api.baseUrl}${deriveBasePath(config)}/resolve-image-urls`;
672
342
  }
673
343
  function buildRequestHeaders(config) {
674
- const headers = {
675
- ...config.api.headers
676
- };
344
+ const headers = { ...config.api.headers };
677
345
  if (config.api.authToken) {
678
346
  headers.Authorization = `Bearer ${config.api.authToken}`;
679
347
  }
680
348
  return headers;
681
349
  }
350
+ function buildScopeKey(config) {
351
+ return [
352
+ config.session?.userId ?? "",
353
+ config.workflow.id ?? "",
354
+ config.workflow.version ?? "",
355
+ config.workflow.stage ?? ""
356
+ ].join("|");
357
+ }
682
358
 
683
359
  // src/utils/userActionClient.ts
684
360
  async function sendUserActionRequest(config, userActionId, action, data) {
@@ -688,690 +364,103 @@ async function sendUserActionRequest(config, userActionId, action, data) {
688
364
  const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
689
365
  const response = await fetch(url, {
690
366
  method: "POST",
691
- headers,
692
- body: hasBody ? JSON.stringify(data) : void 0
693
- });
694
- if (!response.ok) {
695
- const errorText = await response.text();
696
- throw new Error(`HTTP ${response.status}: ${errorText}`);
697
- }
698
- return await response.json();
699
- }
700
- async function submitUserAction(config, userActionId, data) {
701
- return sendUserActionRequest(config, userActionId, "submit", data);
702
- }
703
- async function cancelUserAction(config, userActionId) {
704
- return sendUserActionRequest(config, userActionId, "cancel");
705
- }
706
- async function resendUserAction(config, userActionId) {
707
- return sendUserActionRequest(config, userActionId, "resend");
708
- }
709
-
710
- // src/utils/chatStore.ts
711
- var memoryStore = /* @__PURE__ */ new Map();
712
- var chatStore = {
713
- get(key) {
714
- return memoryStore.get(key) ?? [];
715
- },
716
- set(key, messages) {
717
- memoryStore.set(key, messages);
718
- },
719
- delete(key) {
720
- memoryStore.delete(key);
721
- }
722
- };
723
-
724
- // src/utils/activeStreamStore.ts
725
- var streams = /* @__PURE__ */ new Map();
726
- var activeStreamStore = {
727
- has(key) {
728
- return streams.has(key);
729
- },
730
- get(key) {
731
- const entry = streams.get(key);
732
- if (!entry) return null;
733
- return { messages: entry.messages, isWaiting: entry.isWaiting };
734
- },
735
- // Called before startStream — registers the controller and initial messages
736
- start(key, abortController, initialMessages) {
737
- const existing = streams.get(key);
738
- streams.set(key, {
739
- messages: initialMessages,
740
- isWaiting: true,
741
- abortController,
742
- listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
743
- });
744
- },
745
- // Called by the stream on every event — applies the same updater pattern React uses
746
- applyMessages(key, updater) {
747
- const entry = streams.get(key);
748
- if (!entry) return;
749
- const next = typeof updater === "function" ? updater(entry.messages) : updater;
750
- entry.messages = next;
751
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
752
- },
753
- setWaiting(key, waiting) {
754
- const entry = streams.get(key);
755
- if (!entry) return;
756
- entry.isWaiting = waiting;
757
- entry.listeners.forEach((l) => l(entry.messages, waiting));
758
- },
759
- // Called when stream completes — persists to chatStore and cleans up
760
- complete(key) {
761
- const entry = streams.get(key);
762
- if (!entry) return;
763
- entry.isWaiting = false;
764
- entry.listeners.forEach((l) => l(entry.messages, false));
765
- const toSave = entry.messages.filter((m) => !m.isStreaming);
766
- if (toSave.length > 0) chatStore.set(key, toSave);
767
- streams.delete(key);
768
- },
769
- // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
770
- subscribe(key, listener) {
771
- const entry = streams.get(key);
772
- if (!entry) return () => {
773
- };
774
- entry.listeners.add(listener);
775
- return () => {
776
- streams.get(key)?.listeners.delete(listener);
777
- };
778
- },
779
- // Explicit user cancel — aborts the controller and removes the entry
780
- abort(key) {
781
- const entry = streams.get(key);
782
- if (!entry) return;
783
- entry.abortController.abort();
784
- streams.delete(key);
785
- }
786
- };
787
-
788
- // src/utils/ragImageResolver.ts
789
- var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
790
- function hasRagImages(content) {
791
- return RAG_IMAGE_REGEX.test(content);
792
- }
793
- async function waitForNextPaint(signal) {
794
- if (signal?.aborted) return;
795
- await new Promise((resolve) => {
796
- let isSettled = false;
797
- const finish = () => {
798
- if (isSettled) return;
799
- isSettled = true;
800
- signal?.removeEventListener("abort", finish);
801
- resolve();
802
- };
803
- signal?.addEventListener("abort", finish, { once: true });
804
- if (typeof requestAnimationFrame === "function") {
805
- requestAnimationFrame(() => {
806
- setTimeout(finish, 0);
807
- });
808
- return;
809
- }
810
- setTimeout(finish, 0);
811
- });
812
- }
813
- async function resolveRagImageUrls(config, content, signal) {
814
- const url = buildResolveImagesUrl(config);
815
- const baseHeaders = buildRequestHeaders(config);
816
- const headers = { "Content-Type": "application/json", ...baseHeaders };
817
- const response = await fetch(url, {
818
- method: "POST",
819
- headers,
820
- body: JSON.stringify({ input: content }),
821
- signal
822
- });
823
- if (!response.ok) {
824
- const errorText = await response.text();
825
- throw new Error(`HTTP ${response.status}: ${errorText}`);
826
- }
827
- const text = await response.text();
828
- try {
829
- const parsed = JSON.parse(text);
830
- if (typeof parsed === "string") return parsed;
831
- if (typeof parsed.output === "string") return parsed.output;
832
- if (typeof parsed.result === "string") return parsed.result;
833
- } catch {
834
- }
835
- return text;
836
- }
837
-
838
- // src/hooks/useStreamManager.ts
839
- function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
840
- const abortControllerRef = react.useRef(null);
841
- const configRef = react.useRef(config);
842
- configRef.current = config;
843
- const callbacksRef = react.useRef(callbacks);
844
- callbacksRef.current = callbacks;
845
- const startStream = react.useCallback(
846
- async (userMessage, streamingId, sessionId, externalAbortController) => {
847
- abortControllerRef.current?.abort();
848
- const abortController = externalAbortController ?? new AbortController();
849
- abortControllerRef.current = abortController;
850
- const state = {
851
- accumulatedContent: "",
852
- executionId: void 0,
853
- currentSessionId: void 0,
854
- finalData: void 0,
855
- steps: [],
856
- stepCounter: 0,
857
- currentExecutingStepId: void 0,
858
- currentThinkingStepId: void 0,
859
- hasError: false,
860
- errorMessage: "",
861
- userActionRequest: void 0,
862
- userActionPending: false,
863
- userActionResult: void 0,
864
- allThinkingText: "",
865
- inOrchestratorPhase: false,
866
- inAggregatorPhase: false
867
- };
868
- const THROTTLE_MS = 120;
869
- const CHARS_PER_TICK = 10;
870
- const displayedLengthRef = { current: 0 };
871
- let throttleIntervalId = null;
872
- const getActiveStepMessage = () => state.steps.find((s) => s.id === state.currentExecutingStepId)?.message || [...state.steps].reverse().find((s) => s.status === "in_progress")?.message;
873
- const advanceDisplayLength = (full) => {
874
- let newLen = Math.min(displayedLengthRef.current + CHARS_PER_TICK, full.length);
875
- if (newLen > 0 && newLen < full.length) {
876
- const code = full.charCodeAt(newLen - 1);
877
- if (code >= 55296 && code <= 56319) {
878
- newLen = Math.min(newLen + 1, full.length);
879
- }
880
- }
881
- displayedLengthRef.current = newLen;
882
- };
883
- const clearThrottle = () => {
884
- if (throttleIntervalId != null) {
885
- clearInterval(throttleIntervalId);
886
- throttleIntervalId = null;
887
- }
888
- };
889
- abortController.signal.addEventListener("abort", clearThrottle, { once: true });
890
- const ensureThrottle = () => {
891
- if (throttleIntervalId != null) return;
892
- throttleIntervalId = setInterval(() => {
893
- if (abortController.signal.aborted) {
894
- clearThrottle();
895
- return;
896
- }
897
- const full = state.activeThinkingText ?? "";
898
- if (displayedLengthRef.current < full.length) {
899
- advanceDisplayLength(full);
900
- const displayText = full.slice(0, displayedLengthRef.current);
901
- setMessages(
902
- (prev) => prev.map(
903
- (msg) => msg.id === streamingId ? {
904
- ...msg,
905
- ...createStreamingMessageUpdate({
906
- ...state,
907
- activeThinkingText: displayText,
908
- currentMessage: getActiveStepMessage() || "Thinking..."
909
- })
910
- } : msg
911
- )
912
- );
913
- }
914
- }, THROTTLE_MS);
915
- };
916
- const currentConfig = configRef.current;
917
- const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
918
- const url = buildStreamingUrl(currentConfig);
919
- const headers = buildRequestHeaders(currentConfig);
920
- try {
921
- await streamWorkflowEvents(url, requestBody, headers, {
922
- signal: abortController.signal,
923
- onEvent: (event) => {
924
- if (abortController.signal.aborted) {
925
- return;
926
- }
927
- if (typeof event.eventType === "string" && event.eventType.toUpperCase() === "KEEP_ALIVE") {
928
- if (event.executionId) state.executionId = event.executionId;
929
- if (event.sessionId) state.currentSessionId = event.sessionId;
930
- return;
931
- }
932
- if (event.executionId) state.executionId = event.executionId;
933
- if (event.sessionId) state.currentSessionId = event.sessionId;
934
- const activeThinkingLengthBeforeProcess = state.activeThinkingText?.length ?? 0;
935
- processStreamEvent(event, state);
936
- const eventType = event.eventType;
937
- if (eventType === "INTENT_THINKING") {
938
- displayedLengthRef.current = 0;
939
- ensureThrottle();
940
- } else if (eventType !== "INTENT_THINKING_CONT") {
941
- displayedLengthRef.current = activeThinkingLengthBeforeProcess;
942
- clearThrottle();
943
- }
944
- if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
945
- callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
946
- } else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
947
- const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
948
- callbacksRef.current.onUserActionEvent?.(eventType, msg);
949
- }
950
- const isIntentThinkingEvent = eventType === "INTENT_THINKING" || eventType === "INTENT_THINKING_CONT";
951
- const rawMessage = event.message?.trim() || event.errorMessage?.trim();
952
- const currentMessage = isIntentThinkingEvent ? getActiveStepMessage() || "Thinking..." : rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : getActiveStepMessage() || getEventMessage(event));
953
- const displayThinking = state.activeThinkingText != null ? state.activeThinkingText.slice(0, displayedLengthRef.current) : state.activeThinkingText;
954
- setMessages(
955
- (prev) => prev.map(
956
- (msg) => msg.id === streamingId ? {
957
- ...msg,
958
- ...createStreamingMessageUpdate({
959
- ...state,
960
- activeThinkingText: displayThinking,
961
- currentMessage
962
- })
963
- } : msg
964
- )
965
- );
966
- },
967
- onError: (error) => {
968
- clearThrottle();
969
- setIsWaitingForResponse(false);
970
- if (error.name !== "AbortError") {
971
- callbacksRef.current.onError?.(error);
972
- }
973
- if (state.userActionPending) {
974
- state.userActionPending = false;
975
- state.userActionRequest = void 0;
976
- callbacksRef.current.onUserActionEvent?.(
977
- "USER_ACTION_FAILED",
978
- "Connection lost. Please try again."
979
- );
980
- }
981
- setMessages(
982
- (prev) => prev.map(
983
- (msg) => msg.id === streamingId ? {
984
- ...msg,
985
- ...createErrorMessageUpdate(error, state)
986
- } : msg
987
- )
988
- );
989
- },
990
- onComplete: () => {
991
- clearThrottle();
992
- setIsWaitingForResponse(false);
993
- if (state.userActionPending) {
994
- state.userActionPending = false;
995
- state.userActionRequest = void 0;
996
- callbacksRef.current.onUserActionEvent?.(
997
- "USER_ACTION_FAILED",
998
- "Verification could not be completed."
999
- );
1000
- }
1001
- if (state.currentSessionId && state.currentSessionId !== sessionId) {
1002
- callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
1003
- }
1004
- const needsImageResolve = !state.hasError && !abortController.signal.aborted && hasRagImages(state.accumulatedContent);
1005
- const finalMessage = createFinalMessage(streamingId, {
1006
- ...state,
1007
- sessionId: state.currentSessionId || sessionId,
1008
- isResolvingImages: needsImageResolve
1009
- });
1010
- setMessages(
1011
- (prev) => prev.map(
1012
- (msg) => msg.id === streamingId ? finalMessage : msg
1013
- )
1014
- );
1015
- callbacksRef.current.onStreamComplete?.(finalMessage);
1016
- }
1017
- });
1018
- clearThrottle();
1019
- const shouldResolveImages = !abortController.signal.aborted && !state.hasError && hasRagImages(state.accumulatedContent);
1020
- if (shouldResolveImages) {
1021
- await waitForNextPaint(abortController.signal);
1022
- }
1023
- if (shouldResolveImages && !abortController.signal.aborted) {
1024
- try {
1025
- const resolvedContent = await resolveRagImageUrls(
1026
- currentConfig,
1027
- state.accumulatedContent,
1028
- abortController.signal
1029
- );
1030
- setMessages(
1031
- (prev) => prev.map(
1032
- (msg) => msg.id === streamingId ? { ...msg, content: resolvedContent, isResolvingImages: false } : msg
1033
- )
1034
- );
1035
- } catch {
1036
- setMessages(
1037
- (prev) => prev.map(
1038
- (msg) => msg.id === streamingId ? { ...msg, isResolvingImages: false } : msg
1039
- )
1040
- );
1041
- }
1042
- }
1043
- return state.currentSessionId;
1044
- } catch (error) {
1045
- clearThrottle();
1046
- setIsWaitingForResponse(false);
1047
- if (error.name !== "AbortError") {
1048
- callbacksRef.current.onError?.(error);
1049
- }
1050
- if (state.userActionPending) {
1051
- state.userActionPending = false;
1052
- state.userActionRequest = void 0;
1053
- callbacksRef.current.onUserActionEvent?.(
1054
- "USER_ACTION_FAILED",
1055
- "Connection lost. Please try again."
1056
- );
1057
- }
1058
- setMessages(
1059
- (prev) => prev.map(
1060
- (msg) => msg.id === streamingId ? {
1061
- ...msg,
1062
- ...createErrorMessageUpdate(error, state)
1063
- } : msg
1064
- )
1065
- );
1066
- return state.currentSessionId;
1067
- }
1068
- },
1069
- [setMessages, setIsWaitingForResponse]
1070
- );
1071
- const cancelStream = react.useCallback(() => {
1072
- abortControllerRef.current?.abort();
1073
- }, []);
1074
- return {
1075
- startStream,
1076
- cancelStream,
1077
- abortControllerRef
1078
- };
1079
- }
1080
-
1081
- // src/hooks/useChat.ts
1082
- function useChat(config, callbacks = {}) {
1083
- const [messages, setMessages] = react.useState(() => {
1084
- if (config.userId) return chatStore.get(config.userId);
1085
- return config.initialMessages ?? [];
1086
- });
1087
- const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1088
- const sessionIdRef = react.useRef(
1089
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
1090
- );
1091
- const prevUserIdRef = react.useRef(config.userId);
1092
- const callbacksRef = react.useRef(callbacks);
1093
- callbacksRef.current = callbacks;
1094
- const configRef = react.useRef(config);
1095
- configRef.current = config;
1096
- const messagesRef = react.useRef(messages);
1097
- messagesRef.current = messages;
1098
- const storeAwareSetMessages = react.useCallback(
1099
- (updater) => {
1100
- const { userId } = configRef.current;
1101
- if (userId && activeStreamStore.has(userId)) {
1102
- activeStreamStore.applyMessages(userId, updater);
1103
- }
1104
- setMessages(updater);
1105
- },
1106
- // eslint-disable-next-line react-hooks/exhaustive-deps
1107
- []
1108
- );
1109
- const storeAwareSetIsWaiting = react.useCallback(
1110
- (waiting) => {
1111
- const { userId } = configRef.current;
1112
- if (userId && activeStreamStore.has(userId)) {
1113
- activeStreamStore.setWaiting(userId, waiting);
1114
- }
1115
- setIsWaitingForResponse(waiting);
1116
- },
1117
- // eslint-disable-next-line react-hooks/exhaustive-deps
1118
- []
1119
- );
1120
- const [userActionState, setUserActionState] = react.useState({
1121
- request: null,
1122
- result: null,
1123
- clearOtpTrigger: 0
1124
- });
1125
- const userActionStateRef = react.useRef(userActionState);
1126
- userActionStateRef.current = userActionState;
1127
- const wrappedCallbacks = react.useMemo(() => ({
1128
- ...callbacksRef.current,
1129
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1130
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1131
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1132
- onError: (error) => callbacksRef.current.onError?.(error),
1133
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1134
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1135
- onUserActionRequired: (request) => {
1136
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1137
- callbacksRef.current.onUserActionRequired?.(request);
1138
- },
1139
- onUserActionEvent: (eventType, message) => {
1140
- switch (eventType) {
1141
- case "USER_ACTION_SUCCESS":
1142
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1143
- break;
1144
- case "USER_ACTION_REJECTED":
1145
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1146
- break;
1147
- case "USER_ACTION_EXPIRED":
1148
- case "USER_ACTION_FAILED":
1149
- setUserActionState((prev) => ({ ...prev, request: null }));
1150
- break;
1151
- case "USER_ACTION_INVALID":
1152
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1153
- break;
1154
- }
1155
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1156
- }
1157
- // eslint-disable-next-line react-hooks/exhaustive-deps
1158
- }), []);
1159
- const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
1160
- config,
1161
- wrappedCallbacks,
1162
- storeAwareSetMessages,
1163
- storeAwareSetIsWaiting
1164
- );
1165
- const sendMessage = react.useCallback(
1166
- async (userMessage) => {
1167
- if (!userMessage.trim()) return;
1168
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1169
- sessionIdRef.current = generateId();
1170
- callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1171
- }
1172
- const userMessageId = `user-${Date.now()}`;
1173
- const userMsg = {
1174
- id: userMessageId,
1175
- sessionId: sessionIdRef.current,
1176
- role: "user",
1177
- content: userMessage,
1178
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1179
- };
1180
- setMessages((prev) => [...prev, userMsg]);
1181
- callbacksRef.current.onMessageSent?.(userMessage);
1182
- setIsWaitingForResponse(true);
1183
- callbacksRef.current.onStreamStart?.();
1184
- const streamingId = `assistant-${Date.now()}`;
1185
- const streamingMsg = {
1186
- id: streamingId,
1187
- sessionId: sessionIdRef.current,
1188
- role: "assistant",
1189
- content: "",
1190
- streamingContent: "",
1191
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1192
- isStreaming: true,
1193
- streamProgress: "started",
1194
- steps: [],
1195
- currentExecutingStepId: void 0,
1196
- isCancelled: false,
1197
- currentMessage: void 0
1198
- };
1199
- setMessages((prev) => [...prev, streamingMsg]);
1200
- const abortController = new AbortController();
1201
- const { userId } = configRef.current;
1202
- if (userId) {
1203
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1204
- activeStreamStore.start(userId, abortController, initialMessages);
1205
- }
1206
- const newSessionId = await startStream(
1207
- userMessage,
1208
- streamingId,
1209
- sessionIdRef.current,
1210
- abortController
1211
- );
1212
- if (userId) {
1213
- activeStreamStore.complete(userId);
1214
- }
1215
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1216
- sessionIdRef.current = newSessionId;
1217
- }
1218
- },
1219
- [startStream]
1220
- );
1221
- const clearMessages = react.useCallback(() => {
1222
- if (configRef.current.userId) {
1223
- chatStore.delete(configRef.current.userId);
1224
- }
1225
- setMessages([]);
1226
- }, []);
1227
- const prependMessages = react.useCallback((msgs) => {
1228
- setMessages((prev) => [...msgs, ...prev]);
1229
- }, []);
1230
- const cancelStream = react.useCallback(() => {
1231
- if (configRef.current.userId) {
1232
- activeStreamStore.abort(configRef.current.userId);
1233
- }
1234
- cancelStreamManager();
1235
- setIsWaitingForResponse(false);
1236
- setUserActionState((prev) => ({ ...prev, request: null, result: null }));
1237
- setMessages(
1238
- (prev) => prev.map((msg) => {
1239
- if (msg.isStreaming) {
1240
- return {
1241
- ...msg,
1242
- ...createCancelledMessageUpdate(
1243
- msg.steps || [],
1244
- msg.currentMessage
1245
- )
1246
- };
1247
- }
1248
- return msg;
1249
- })
1250
- );
1251
- }, [cancelStreamManager]);
1252
- const resetSession = react.useCallback(() => {
1253
- if (configRef.current.userId) {
1254
- activeStreamStore.abort(configRef.current.userId);
1255
- chatStore.delete(configRef.current.userId);
1256
- }
1257
- setMessages([]);
1258
- sessionIdRef.current = void 0;
1259
- abortControllerRef.current?.abort();
1260
- setIsWaitingForResponse(false);
1261
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1262
- }, []);
1263
- const getSessionId = react.useCallback(() => {
1264
- return sessionIdRef.current;
1265
- }, []);
1266
- const getMessages = react.useCallback(() => {
1267
- return messages;
1268
- }, [messages]);
1269
- const approveUserAction = react.useCallback(
1270
- async (otp) => {
1271
- const request = userActionStateRef.current.request;
1272
- if (!request) return;
1273
- try {
1274
- await submitUserAction(configRef.current, request.userActionId, { otp });
1275
- } catch (error) {
1276
- setUserActionState((prev) => ({
1277
- ...prev,
1278
- clearOtpTrigger: prev.clearOtpTrigger + 1
1279
- }));
1280
- callbacksRef.current.onError?.(error);
1281
- throw error;
1282
- }
1283
- },
1284
- []
1285
- );
1286
- const rejectUserAction = react.useCallback(async () => {
1287
- const request = userActionStateRef.current.request;
1288
- if (!request) return;
1289
- try {
1290
- setMessages((prev) => {
1291
- let lastStreamingIdx = -1;
1292
- for (let i = prev.length - 1; i >= 0; i--) {
1293
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
1294
- lastStreamingIdx = i;
1295
- break;
1296
- }
1297
- }
1298
- if (lastStreamingIdx === -1) return prev;
1299
- return prev.map(
1300
- (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
1301
- );
1302
- });
1303
- await cancelUserAction(configRef.current, request.userActionId);
1304
- } catch (error) {
1305
- callbacksRef.current.onError?.(error);
1306
- throw error;
1307
- }
1308
- }, []);
1309
- const resendOtp = react.useCallback(async () => {
1310
- const request = userActionStateRef.current.request;
1311
- if (!request) return;
1312
- try {
1313
- await resendUserAction(configRef.current, request.userActionId);
1314
- } catch (error) {
1315
- callbacksRef.current.onError?.(error);
1316
- throw error;
1317
- }
1318
- }, []);
1319
- react.useEffect(() => {
1320
- const { userId } = config;
1321
- if (!userId) return;
1322
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1323
- setMessages(msgs);
1324
- setIsWaitingForResponse(isWaiting);
1325
- });
1326
- const active = activeStreamStore.get(userId);
1327
- if (active) {
1328
- setMessages(active.messages);
1329
- setIsWaitingForResponse(active.isWaiting);
1330
- }
1331
- return unsubscribe;
1332
- }, []);
1333
- react.useEffect(() => {
1334
- if (!config.userId) return;
1335
- const toSave = messages.filter((m) => !m.isStreaming);
1336
- if (toSave.length > 0) {
1337
- chatStore.set(config.userId, toSave);
1338
- }
1339
- }, [messages, config.userId]);
1340
- react.useEffect(() => {
1341
- const prevUserId = prevUserIdRef.current;
1342
- prevUserIdRef.current = config.userId;
1343
- if (prevUserId === config.userId) return;
1344
- if (prevUserId && !config.userId) {
1345
- chatStore.delete(prevUserId);
1346
- setMessages([]);
1347
- sessionIdRef.current = void 0;
1348
- setIsWaitingForResponse(false);
1349
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1350
- } else if (config.userId) {
1351
- const stored = chatStore.get(config.userId);
1352
- setMessages(stored);
1353
- sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
1354
- }
1355
- }, [config.userId]);
1356
- return {
1357
- messages,
1358
- sendMessage,
1359
- clearMessages,
1360
- prependMessages,
1361
- cancelStream,
1362
- resetSession,
1363
- getSessionId,
1364
- getMessages,
1365
- isWaitingForResponse,
1366
- sessionId: sessionIdRef.current,
1367
- // User action (OTP) state and methods
1368
- userActionState,
1369
- approveUserAction,
1370
- rejectUserAction,
1371
- resendOtp
1372
- };
367
+ headers,
368
+ body: hasBody ? JSON.stringify(data) : void 0
369
+ });
370
+ if (!response.ok) {
371
+ const errorText = await response.text();
372
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
373
+ }
374
+ return await response.json();
375
+ }
376
+ async function submitUserAction(config, userActionId, data) {
377
+ return sendUserActionRequest(config, userActionId, "submit", data);
378
+ }
379
+ async function cancelUserAction(config, userActionId) {
380
+ return sendUserActionRequest(config, userActionId, "cancel");
381
+ }
382
+ async function resendUserAction(config, userActionId) {
383
+ return sendUserActionRequest(config, userActionId, "resend");
1373
384
  }
1374
385
 
386
+ // src/utils/chatStore.ts
387
+ var memoryStore = /* @__PURE__ */ new Map();
388
+ var chatStore = {
389
+ get(key) {
390
+ return memoryStore.get(key) ?? [];
391
+ },
392
+ set(key, messages) {
393
+ memoryStore.set(key, messages);
394
+ },
395
+ delete(key) {
396
+ memoryStore.delete(key);
397
+ }
398
+ };
399
+
400
+ // src/utils/activeStreamStore.ts
401
+ var streams = /* @__PURE__ */ new Map();
402
+ var activeStreamStore = {
403
+ has(key) {
404
+ return streams.has(key);
405
+ },
406
+ get(key) {
407
+ const entry = streams.get(key);
408
+ if (!entry) return null;
409
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
410
+ },
411
+ // Called before startStream — registers the controller and initial messages
412
+ start(key, abortController, initialMessages) {
413
+ const existing = streams.get(key);
414
+ streams.set(key, {
415
+ messages: initialMessages,
416
+ isWaiting: true,
417
+ abortController,
418
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
419
+ });
420
+ },
421
+ // Called by the stream on every event — applies the same updater pattern React uses
422
+ applyMessages(key, updater) {
423
+ const entry = streams.get(key);
424
+ if (!entry) return;
425
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
426
+ entry.messages = next;
427
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
428
+ },
429
+ setWaiting(key, waiting) {
430
+ const entry = streams.get(key);
431
+ if (!entry) return;
432
+ entry.isWaiting = waiting;
433
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
434
+ },
435
+ // Called when stream completes — persists to chatStore and cleans up
436
+ complete(key) {
437
+ const entry = streams.get(key);
438
+ if (!entry) return;
439
+ entry.isWaiting = false;
440
+ entry.listeners.forEach((l) => l(entry.messages, false));
441
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
442
+ if (toSave.length > 0) chatStore.set(key, toSave);
443
+ streams.delete(key);
444
+ },
445
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
446
+ subscribe(key, listener) {
447
+ const entry = streams.get(key);
448
+ if (!entry) return () => {
449
+ };
450
+ entry.listeners.add(listener);
451
+ return () => {
452
+ streams.get(key)?.listeners.delete(listener);
453
+ };
454
+ },
455
+ // Explicit user cancel — aborts the controller and removes the entry
456
+ abort(key) {
457
+ const entry = streams.get(key);
458
+ if (!entry) return;
459
+ entry.abortController.abort();
460
+ streams.delete(key);
461
+ }
462
+ };
463
+
1375
464
  // src/utils/v2EventProcessor.ts
1376
465
  function getEventText(event, field) {
1377
466
  const value = event[field];
@@ -1396,7 +485,7 @@ function addThinkingLine(state, header, detail) {
1396
485
  function appendThinkingText(state, text) {
1397
486
  state.formattedThinkingText += text;
1398
487
  }
1399
- function completeLastInProgressStep2(steps) {
488
+ function completeLastInProgressStep(steps) {
1400
489
  for (let i = steps.length - 1; i >= 0; i--) {
1401
490
  if (steps[i].status === "in_progress") {
1402
491
  steps[i].status = "completed";
@@ -1493,7 +582,19 @@ function processStreamEventV2(event, state) {
1493
582
  break;
1494
583
  }
1495
584
  case "ORCHESTRATOR_COMPLETED": {
1496
- appendThinkingText(state, "\n" + (event.message || "Planning complete"));
585
+ const workingDetail = workingPhaseDetailForDisplay(message);
586
+ if (workingDetail) {
587
+ addThinkingLine(state, "**Working**", workingDetail);
588
+ } else {
589
+ addThinkingHeader(state, "**Working**");
590
+ }
591
+ state.steps.push({
592
+ id: `step-${state.stepCounter++}`,
593
+ eventType: "WORKING",
594
+ message: workingDetail,
595
+ status: "completed",
596
+ timestamp: Date.now()
597
+ });
1497
598
  const step = state.steps.find((s) => s.eventType === "ORCHESTRATOR_THINKING" && s.status === "in_progress");
1498
599
  if (step) {
1499
600
  step.status = "completed";
@@ -1598,7 +699,7 @@ function processStreamEventV2(event, state) {
1598
699
  break;
1599
700
  }
1600
701
  case "USER_ACTION_REQUIRED": {
1601
- completeLastInProgressStep2(state.steps);
702
+ completeLastInProgressStep(state.steps);
1602
703
  if (event.userActionRequest) {
1603
704
  state.userActionRequest = {
1604
705
  userActionId: event.userActionRequest.userActionId,
@@ -1628,7 +729,7 @@ function processStreamEventV2(event, state) {
1628
729
  }
1629
730
  case "USER_ACTION_SUCCESS": {
1630
731
  appendThinkingText(state, "\n\u2713 " + (event.message || "Verification successful"));
1631
- completeLastInProgressStep2(state.steps);
732
+ completeLastInProgressStep(state.steps);
1632
733
  state.userActionRequest = void 0;
1633
734
  state.userActionPending = false;
1634
735
  state.userActionResult = "approved";
@@ -1645,7 +746,7 @@ function processStreamEventV2(event, state) {
1645
746
  break;
1646
747
  }
1647
748
  case "USER_ACTION_INVALID": {
1648
- completeLastInProgressStep2(state.steps);
749
+ completeLastInProgressStep(state.steps);
1649
750
  const errorStepId = `step-${state.stepCounter++}`;
1650
751
  state.steps.push({
1651
752
  id: errorStepId,
@@ -1669,7 +770,7 @@ function processStreamEventV2(event, state) {
1669
770
  }
1670
771
  case "USER_ACTION_REJECTED": {
1671
772
  appendThinkingText(state, "\n\u2717 " + (event.message || "Verification rejected"));
1672
- completeLastInProgressStep2(state.steps);
773
+ completeLastInProgressStep(state.steps);
1673
774
  state.userActionRequest = void 0;
1674
775
  state.userActionPending = false;
1675
776
  state.userActionResult = "rejected";
@@ -1687,7 +788,7 @@ function processStreamEventV2(event, state) {
1687
788
  }
1688
789
  case "USER_ACTION_EXPIRED": {
1689
790
  appendThinkingText(state, "\n\u2717 " + (event.message || "Verification expired"));
1690
- completeLastInProgressStep2(state.steps);
791
+ completeLastInProgressStep(state.steps);
1691
792
  state.userActionRequest = void 0;
1692
793
  state.userActionPending = false;
1693
794
  const stepId = `step-${state.stepCounter++}`;
@@ -1717,7 +818,7 @@ function processStreamEventV2(event, state) {
1717
818
  }
1718
819
  case "USER_ACTION_FAILED": {
1719
820
  appendThinkingText(state, "\n\u2717 " + (event.message || "Verification failed"));
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++}`;
@@ -1755,8 +856,58 @@ function processStreamEventV2(event, state) {
1755
856
  return state;
1756
857
  }
1757
858
 
859
+ // src/utils/ragImageResolver.ts
860
+ var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
861
+ function hasRagImages(content) {
862
+ return RAG_IMAGE_REGEX.test(content);
863
+ }
864
+ async function waitForNextPaint(signal) {
865
+ if (signal?.aborted) return;
866
+ await new Promise((resolve) => {
867
+ let isSettled = false;
868
+ const finish = () => {
869
+ if (isSettled) return;
870
+ isSettled = true;
871
+ signal?.removeEventListener("abort", finish);
872
+ resolve();
873
+ };
874
+ signal?.addEventListener("abort", finish, { once: true });
875
+ if (typeof requestAnimationFrame === "function") {
876
+ requestAnimationFrame(() => {
877
+ setTimeout(finish, 0);
878
+ });
879
+ return;
880
+ }
881
+ setTimeout(finish, 0);
882
+ });
883
+ }
884
+ async function resolveRagImageUrls(config, content, signal) {
885
+ const url = buildResolveImagesUrl(config);
886
+ const baseHeaders = buildRequestHeaders(config);
887
+ const headers = { "Content-Type": "application/json", ...baseHeaders };
888
+ const response = await fetch(url, {
889
+ method: "POST",
890
+ headers,
891
+ body: JSON.stringify({ input: content }),
892
+ signal
893
+ });
894
+ if (!response.ok) {
895
+ const errorText = await response.text();
896
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
897
+ }
898
+ const text = await response.text();
899
+ try {
900
+ const parsed = JSON.parse(text);
901
+ if (typeof parsed === "string") return parsed;
902
+ if (typeof parsed.output === "string") return parsed.output;
903
+ if (typeof parsed.result === "string") return parsed.result;
904
+ } catch {
905
+ }
906
+ return text;
907
+ }
908
+
1758
909
  // src/hooks/useStreamManagerV2.ts
1759
- var FRIENDLY_ERROR_MESSAGE2 = "Oops, something went wrong. Please try again.";
910
+ var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
1760
911
  function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForResponse) {
1761
912
  const abortControllerRef = react.useRef(null);
1762
913
  const configRef = react.useRef(config);
@@ -1804,8 +955,8 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1804
955
  const currentMessage = activeStep?.message || lastInProgressStep?.message || getEventMessage(event);
1805
956
  if (state.hasError) {
1806
957
  updateMessage({
1807
- streamingContent: FRIENDLY_ERROR_MESSAGE2,
1808
- content: FRIENDLY_ERROR_MESSAGE2,
958
+ streamingContent: FRIENDLY_ERROR_MESSAGE,
959
+ content: FRIENDLY_ERROR_MESSAGE,
1809
960
  streamProgress: "error",
1810
961
  isError: true,
1811
962
  errorDetails: state.errorMessage,
@@ -1855,7 +1006,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1855
1006
  isError: !isAborted,
1856
1007
  isCancelled: isAborted,
1857
1008
  errorDetails: isAborted ? void 0 : error.message,
1858
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1009
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1859
1010
  currentMessage: isAborted ? "Thinking..." : void 0,
1860
1011
  formattedThinkingText: state.formattedThinkingText || void 0,
1861
1012
  steps: [...state.steps].map((step) => {
@@ -1887,7 +1038,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1887
1038
  id: streamingId,
1888
1039
  sessionId: state.sessionId || sessionId,
1889
1040
  role: "assistant",
1890
- content: state.hasError ? FRIENDLY_ERROR_MESSAGE2 : state.finalResponse || "",
1041
+ content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.finalResponse || "",
1891
1042
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1892
1043
  isStreaming: false,
1893
1044
  streamProgress: state.hasError ? "error" : "completed",
@@ -1958,7 +1109,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1958
1109
  isError: !isAborted,
1959
1110
  isCancelled: isAborted,
1960
1111
  errorDetails: isAborted ? void 0 : error.message,
1961
- content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE2,
1112
+ content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1962
1113
  formattedThinkingText: state.formattedThinkingText || void 0,
1963
1114
  steps: [...state.steps].map((step) => {
1964
1115
  if (step.status === "in_progress" && isAborted) {
@@ -1985,45 +1136,199 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1985
1136
  };
1986
1137
  }
1987
1138
 
1139
+ // src/utils/workflowUsersClient.ts
1140
+ var DEFAULT_SESSION_PAGE_SIZE = 20;
1141
+ var DEFAULT_CONVERSATION_PAGE_SIZE = 50;
1142
+ function getStageParamName2(config) {
1143
+ return config.api.stageQueryParam ?? "stage";
1144
+ }
1145
+ function requireOwnerId(config) {
1146
+ const ownerId = config.session?.owner?.id;
1147
+ if (!ownerId) {
1148
+ throw new Error(
1149
+ "workflowUsersClient: session.owner.id is required to call this endpoint."
1150
+ );
1151
+ }
1152
+ return ownerId;
1153
+ }
1154
+ function requireWorkflowName(config) {
1155
+ const workflowName = config.workflow.name;
1156
+ if (!workflowName) {
1157
+ throw new Error(
1158
+ "workflowUsersClient: workflow.name is required to call this endpoint."
1159
+ );
1160
+ }
1161
+ return workflowName;
1162
+ }
1163
+ function requireWorkflowId(config) {
1164
+ const workflowId = config.workflow.id;
1165
+ if (!workflowId) {
1166
+ throw new Error(
1167
+ "workflowUsersClient: workflow.id is required to call this endpoint."
1168
+ );
1169
+ }
1170
+ return workflowId;
1171
+ }
1172
+ function isPlaygroundYaakAuth(config) {
1173
+ if (config.api.authToken) return false;
1174
+ const headers = config.api.headers;
1175
+ if (!headers) return false;
1176
+ for (const key of Object.keys(headers)) {
1177
+ const lower = key.toLowerCase();
1178
+ if (lower === "yaak-api-key" || lower === "x-yaak-api-key") return true;
1179
+ }
1180
+ return false;
1181
+ }
1182
+ async function fetchJson(url, headers, signal) {
1183
+ const response = await fetch(url, {
1184
+ method: "GET",
1185
+ headers,
1186
+ signal
1187
+ });
1188
+ if (!response.ok) {
1189
+ const errorText = await response.text();
1190
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
1191
+ }
1192
+ return await response.json();
1193
+ }
1194
+ async function listSessions(config, opts = {}) {
1195
+ const ownerId = requireOwnerId(config);
1196
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1197
+ const params = new URLSearchParams({
1198
+ page: String(opts.page ?? 0),
1199
+ size: String(opts.size ?? DEFAULT_SESSION_PAGE_SIZE)
1200
+ });
1201
+ if (useYaakEndpoint) {
1202
+ params.set("workflowUserId", ownerId);
1203
+ params.set("workflowName", requireWorkflowName(config));
1204
+ } else {
1205
+ params.set("workflowId", requireWorkflowId(config));
1206
+ }
1207
+ if (config.workflow.stage) {
1208
+ params.set(getStageParamName2(config), config.workflow.stage);
1209
+ }
1210
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/sessions?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1211
+ ownerId
1212
+ )}/sessions?${params.toString()}`;
1213
+ return fetchJson(
1214
+ url,
1215
+ buildRequestHeaders(config),
1216
+ opts.signal
1217
+ );
1218
+ }
1219
+ async function listConversations(config, opts) {
1220
+ const ownerId = requireOwnerId(config);
1221
+ const useYaakEndpoint = isPlaygroundYaakAuth(config);
1222
+ const params = new URLSearchParams({
1223
+ sessionId: opts.sessionId,
1224
+ page: String(opts.page ?? 0),
1225
+ size: String(opts.size ?? DEFAULT_CONVERSATION_PAGE_SIZE)
1226
+ });
1227
+ if (useYaakEndpoint) {
1228
+ params.set("workflowUserId", ownerId);
1229
+ params.set("workflowName", requireWorkflowName(config));
1230
+ }
1231
+ if (config.workflow.stage) {
1232
+ params.set(getStageParamName2(config), config.workflow.stage);
1233
+ }
1234
+ const url = useYaakEndpoint ? `${config.api.baseUrl}/api/workflows/ask/conversations?${params.toString()}` : `${config.api.baseUrl}/api/workflow-users/${encodeURIComponent(
1235
+ ownerId
1236
+ )}/conversations?${params.toString()}`;
1237
+ return fetchJson(
1238
+ url,
1239
+ buildRequestHeaders(config),
1240
+ opts.signal
1241
+ );
1242
+ }
1243
+
1988
1244
  // src/hooks/useChatV2.ts
1245
+ function conversationEntryToMessages(entry, sessionId) {
1246
+ return [
1247
+ {
1248
+ id: `history-user-${entry.executionId}`,
1249
+ sessionId,
1250
+ role: "user",
1251
+ content: entry.query,
1252
+ timestamp: entry.createdAt,
1253
+ isHistorical: true
1254
+ },
1255
+ {
1256
+ id: `history-assistant-${entry.executionId}`,
1257
+ sessionId,
1258
+ role: "assistant",
1259
+ content: entry.response,
1260
+ timestamp: entry.createdAt,
1261
+ executionId: entry.executionId,
1262
+ isHistorical: true
1263
+ }
1264
+ ];
1265
+ }
1266
+ function streamKeyFor(scopeKey, sessionId) {
1267
+ return `${scopeKey}|sid:${sessionId ?? ""}`;
1268
+ }
1989
1269
  function useChatV2(config, callbacks = {}) {
1270
+ const scopeKey = react.useMemo(() => buildScopeKey(config), [
1271
+ config.session?.userId,
1272
+ config.workflow.id,
1273
+ config.workflow.version,
1274
+ config.workflow.stage
1275
+ ]);
1276
+ const initialSessionId = chatStore.get(scopeKey).find((m) => m.sessionId)?.sessionId ?? config.session?.initialId ?? void 0;
1990
1277
  const [messages, setMessages] = react.useState(() => {
1991
- if (config.userId) return chatStore.get(config.userId);
1992
- return config.initialMessages ?? [];
1278
+ const stored = chatStore.get(scopeKey);
1279
+ if (stored.length > 0) return stored;
1280
+ return config.session?.initialMessages ?? [];
1993
1281
  });
1994
1282
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1995
- const sessionIdRef = react.useRef(
1996
- config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
1997
- );
1998
- const prevUserIdRef = react.useRef(config.userId);
1283
+ const [loadingSessionId, setLoadingSessionId] = react.useState(void 0);
1284
+ const [currentSessionId, setCurrentSessionId] = react.useState(initialSessionId);
1285
+ const sessionIdRef = react.useRef(initialSessionId);
1286
+ const prevScopeKeyRef = react.useRef(scopeKey);
1287
+ const activeStreamSessionRef = react.useRef(void 0);
1999
1288
  const callbacksRef = react.useRef(callbacks);
2000
1289
  callbacksRef.current = callbacks;
2001
1290
  const configRef = react.useRef(config);
2002
1291
  configRef.current = config;
1292
+ const scopeKeyRef = react.useRef(scopeKey);
1293
+ scopeKeyRef.current = scopeKey;
2003
1294
  const messagesRef = react.useRef(messages);
2004
1295
  messagesRef.current = messages;
2005
1296
  const storeAwareSetMessages = react.useCallback(
2006
1297
  (updater) => {
2007
- const { userId } = configRef.current;
2008
- if (userId && activeStreamStore.has(userId)) {
2009
- activeStreamStore.applyMessages(userId, updater);
1298
+ const scope = scopeKeyRef.current;
1299
+ const streamSid = activeStreamSessionRef.current;
1300
+ if (streamSid !== void 0) {
1301
+ const streamKey = streamKeyFor(scope, streamSid);
1302
+ if (activeStreamStore.has(streamKey)) {
1303
+ activeStreamStore.applyMessages(
1304
+ streamKey,
1305
+ updater
1306
+ );
1307
+ }
1308
+ if (sessionIdRef.current === streamSid) {
1309
+ setMessages(updater);
1310
+ }
1311
+ return;
2010
1312
  }
2011
1313
  setMessages(updater);
2012
1314
  },
2013
- // eslint-disable-next-line react-hooks/exhaustive-deps
2014
1315
  []
2015
1316
  );
2016
- const storeAwareSetIsWaiting = react.useCallback(
2017
- (waiting) => {
2018
- const { userId } = configRef.current;
2019
- if (userId && activeStreamStore.has(userId)) {
2020
- activeStreamStore.setWaiting(userId, waiting);
1317
+ const storeAwareSetIsWaiting = react.useCallback((waiting) => {
1318
+ const scope = scopeKeyRef.current;
1319
+ const streamSid = activeStreamSessionRef.current;
1320
+ if (streamSid !== void 0) {
1321
+ const streamKey = streamKeyFor(scope, streamSid);
1322
+ if (activeStreamStore.has(streamKey)) {
1323
+ activeStreamStore.setWaiting(streamKey, waiting);
2021
1324
  }
2022
- setIsWaitingForResponse(waiting);
2023
- },
2024
- // eslint-disable-next-line react-hooks/exhaustive-deps
2025
- []
2026
- );
1325
+ if (sessionIdRef.current === streamSid) {
1326
+ setIsWaitingForResponse(waiting);
1327
+ }
1328
+ return;
1329
+ }
1330
+ setIsWaitingForResponse(waiting);
1331
+ }, []);
2027
1332
  const [userActionState, setUserActionState] = react.useState({
2028
1333
  request: null,
2029
1334
  result: null,
@@ -2031,38 +1336,43 @@ function useChatV2(config, callbacks = {}) {
2031
1336
  });
2032
1337
  const userActionStateRef = react.useRef(userActionState);
2033
1338
  userActionStateRef.current = userActionState;
2034
- const wrappedCallbacks = react.useMemo(() => ({
2035
- ...callbacksRef.current,
2036
- onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
2037
- onStreamStart: () => callbacksRef.current.onStreamStart?.(),
2038
- onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
2039
- onError: (error) => callbacksRef.current.onError?.(error),
2040
- onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
2041
- onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
2042
- onUserActionRequired: (request) => {
2043
- setUserActionState((prev) => ({ ...prev, request, result: null }));
2044
- callbacksRef.current.onUserActionRequired?.(request);
2045
- },
2046
- onUserActionEvent: (eventType, message) => {
2047
- switch (eventType) {
2048
- case "USER_ACTION_SUCCESS":
2049
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
2050
- break;
2051
- case "USER_ACTION_REJECTED":
2052
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
2053
- break;
2054
- case "USER_ACTION_EXPIRED":
2055
- case "USER_ACTION_FAILED":
2056
- setUserActionState((prev) => ({ ...prev, request: null }));
2057
- break;
2058
- case "USER_ACTION_INVALID":
2059
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
2060
- break;
1339
+ const wrappedCallbacks = react.useMemo(
1340
+ () => ({
1341
+ ...callbacksRef.current,
1342
+ onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
1343
+ onStreamStart: () => callbacksRef.current.onStreamStart?.(),
1344
+ onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
1345
+ onError: (error) => callbacksRef.current.onError?.(error),
1346
+ onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1347
+ onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1348
+ onUserActionRequired: (request) => {
1349
+ setUserActionState((prev) => ({ ...prev, request, result: null }));
1350
+ callbacksRef.current.onUserActionRequired?.(request);
1351
+ },
1352
+ onUserActionEvent: (eventType, message) => {
1353
+ switch (eventType) {
1354
+ case "USER_ACTION_SUCCESS":
1355
+ setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1356
+ break;
1357
+ case "USER_ACTION_REJECTED":
1358
+ setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1359
+ break;
1360
+ case "USER_ACTION_EXPIRED":
1361
+ case "USER_ACTION_FAILED":
1362
+ setUserActionState((prev) => ({ ...prev, request: null }));
1363
+ break;
1364
+ case "USER_ACTION_INVALID":
1365
+ setUserActionState((prev) => ({
1366
+ ...prev,
1367
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1368
+ }));
1369
+ break;
1370
+ }
1371
+ callbacksRef.current.onUserActionEvent?.(eventType, message);
2061
1372
  }
2062
- callbacksRef.current.onUserActionEvent?.(eventType, message);
2063
- }
2064
- // eslint-disable-next-line react-hooks/exhaustive-deps
2065
- }), []);
1373
+ }),
1374
+ []
1375
+ );
2066
1376
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManagerV2(
2067
1377
  config,
2068
1378
  wrappedCallbacks,
@@ -2072,8 +1382,10 @@ function useChatV2(config, callbacks = {}) {
2072
1382
  const sendMessage = react.useCallback(
2073
1383
  async (userMessage) => {
2074
1384
  if (!userMessage.trim()) return;
2075
- if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1385
+ const autoGen = configRef.current.session?.autoGenerateId;
1386
+ if (!sessionIdRef.current && autoGen !== false) {
2076
1387
  sessionIdRef.current = generateId();
1388
+ setCurrentSessionId(sessionIdRef.current);
2077
1389
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
2078
1390
  }
2079
1391
  const userMessageId = `user-${Date.now()}`;
@@ -2105,38 +1417,40 @@ function useChatV2(config, callbacks = {}) {
2105
1417
  };
2106
1418
  setMessages((prev) => [...prev, streamingMsg]);
2107
1419
  const abortController = new AbortController();
2108
- const { userId } = configRef.current;
2109
- if (userId) {
2110
- const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
2111
- activeStreamStore.start(userId, abortController, initialMessages);
2112
- }
1420
+ const scope = scopeKeyRef.current;
1421
+ const streamSessionId = sessionIdRef.current;
1422
+ const streamKey = streamKeyFor(scope, streamSessionId);
1423
+ activeStreamSessionRef.current = streamSessionId;
1424
+ const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1425
+ activeStreamStore.start(streamKey, abortController, initialMessages);
2113
1426
  const newSessionId = await startStream(
2114
1427
  userMessage,
2115
1428
  streamingId,
2116
- sessionIdRef.current,
1429
+ streamSessionId,
2117
1430
  abortController
2118
1431
  );
2119
- if (userId) {
2120
- activeStreamStore.complete(userId);
2121
- }
2122
- if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1432
+ activeStreamStore.complete(streamKey);
1433
+ activeStreamSessionRef.current = void 0;
1434
+ if (!abortController.signal.aborted && newSessionId && newSessionId !== streamSessionId && sessionIdRef.current === streamSessionId) {
2123
1435
  sessionIdRef.current = newSessionId;
1436
+ setCurrentSessionId(newSessionId);
2124
1437
  }
2125
1438
  },
2126
1439
  [startStream]
2127
1440
  );
2128
1441
  const clearMessages = react.useCallback(() => {
2129
- if (configRef.current.userId) {
2130
- chatStore.delete(configRef.current.userId);
2131
- }
1442
+ chatStore.delete(scopeKeyRef.current);
2132
1443
  setMessages([]);
2133
1444
  }, []);
2134
1445
  const prependMessages = react.useCallback((msgs) => {
2135
1446
  setMessages((prev) => [...msgs, ...prev]);
2136
1447
  }, []);
2137
1448
  const cancelStream = react.useCallback(() => {
2138
- if (configRef.current.userId) {
2139
- activeStreamStore.abort(configRef.current.userId);
1449
+ const scope = scopeKeyRef.current;
1450
+ const viewSid = sessionIdRef.current;
1451
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1452
+ if (activeStreamStore.has(viewStreamKey)) {
1453
+ activeStreamStore.abort(viewStreamKey);
2140
1454
  }
2141
1455
  cancelStreamManager();
2142
1456
  setIsWaitingForResponse(false);
@@ -2146,10 +1460,7 @@ function useChatV2(config, callbacks = {}) {
2146
1460
  if (msg.isStreaming) {
2147
1461
  return {
2148
1462
  ...msg,
2149
- ...createCancelledMessageUpdate(
2150
- msg.steps || [],
2151
- msg.currentMessage
2152
- )
1463
+ ...createCancelledMessageUpdate(msg.steps || [], msg.currentMessage)
2153
1464
  };
2154
1465
  }
2155
1466
  return msg;
@@ -2157,39 +1468,37 @@ function useChatV2(config, callbacks = {}) {
2157
1468
  );
2158
1469
  }, [cancelStreamManager]);
2159
1470
  const resetSession = react.useCallback(() => {
2160
- if (configRef.current.userId) {
2161
- activeStreamStore.abort(configRef.current.userId);
2162
- chatStore.delete(configRef.current.userId);
1471
+ const scope = scopeKeyRef.current;
1472
+ const viewSid = sessionIdRef.current;
1473
+ const viewStreamKey = streamKeyFor(scope, viewSid);
1474
+ if (activeStreamStore.has(viewStreamKey)) {
1475
+ activeStreamStore.abort(viewStreamKey);
2163
1476
  }
1477
+ chatStore.delete(scope);
2164
1478
  setMessages([]);
2165
1479
  sessionIdRef.current = void 0;
1480
+ setCurrentSessionId(void 0);
1481
+ activeStreamSessionRef.current = void 0;
2166
1482
  abortControllerRef.current?.abort();
2167
1483
  setIsWaitingForResponse(false);
2168
1484
  setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2169
1485
  }, []);
2170
- const getSessionId = react.useCallback(() => {
2171
- return sessionIdRef.current;
1486
+ const getSessionId = react.useCallback(() => sessionIdRef.current, []);
1487
+ const getMessages = react.useCallback(() => messages, [messages]);
1488
+ const approveUserAction = react.useCallback(async (otp) => {
1489
+ const request = userActionStateRef.current.request;
1490
+ if (!request) return;
1491
+ try {
1492
+ await submitUserAction(configRef.current, request.userActionId, { otp });
1493
+ } catch (error) {
1494
+ setUserActionState((prev) => ({
1495
+ ...prev,
1496
+ clearOtpTrigger: prev.clearOtpTrigger + 1
1497
+ }));
1498
+ callbacksRef.current.onError?.(error);
1499
+ throw error;
1500
+ }
2172
1501
  }, []);
2173
- const getMessages = react.useCallback(() => {
2174
- return messages;
2175
- }, [messages]);
2176
- const approveUserAction = react.useCallback(
2177
- async (otp) => {
2178
- const request = userActionStateRef.current.request;
2179
- if (!request) return;
2180
- try {
2181
- await submitUserAction(configRef.current, request.userActionId, { otp });
2182
- } catch (error) {
2183
- setUserActionState((prev) => ({
2184
- ...prev,
2185
- clearOtpTrigger: prev.clearOtpTrigger + 1
2186
- }));
2187
- callbacksRef.current.onError?.(error);
2188
- throw error;
2189
- }
2190
- },
2191
- []
2192
- );
2193
1502
  const rejectUserAction = react.useCallback(async () => {
2194
1503
  const request = userActionStateRef.current.request;
2195
1504
  if (!request) return;
@@ -2223,43 +1532,91 @@ function useChatV2(config, callbacks = {}) {
2223
1532
  throw error;
2224
1533
  }
2225
1534
  }, []);
1535
+ const inFlightLoadRef = react.useRef(null);
1536
+ const loadingSessionIdRef = react.useRef(void 0);
1537
+ loadingSessionIdRef.current = loadingSessionId;
1538
+ const loadSession = react.useCallback(async (sessionId) => {
1539
+ const inFlight = inFlightLoadRef.current;
1540
+ if (inFlight && inFlight.sessionId === sessionId) {
1541
+ return inFlight.promise;
1542
+ }
1543
+ if (sessionIdRef.current === sessionId && loadingSessionIdRef.current !== sessionId) {
1544
+ return;
1545
+ }
1546
+ const run = async () => {
1547
+ const scope = scopeKeyRef.current;
1548
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1549
+ sessionIdRef.current = sessionId;
1550
+ setCurrentSessionId(sessionId);
1551
+ callbacksRef.current.onSessionIdChange?.(sessionId);
1552
+ const streamKey = streamKeyFor(scope, sessionId);
1553
+ const active = activeStreamStore.get(streamKey);
1554
+ if (active) {
1555
+ setMessages(active.messages);
1556
+ setIsWaitingForResponse(active.isWaiting);
1557
+ setLoadingSessionId(void 0);
1558
+ return;
1559
+ }
1560
+ setIsWaitingForResponse(false);
1561
+ setMessages([]);
1562
+ chatStore.delete(scope);
1563
+ setLoadingSessionId(sessionId);
1564
+ try {
1565
+ const response = await listConversations(configRef.current, { sessionId });
1566
+ const entries = response.data ?? [];
1567
+ const historical = entries.flatMap((entry) => conversationEntryToMessages(entry, sessionId));
1568
+ if (sessionIdRef.current === sessionId) {
1569
+ setMessages(historical);
1570
+ }
1571
+ if (historical.length > 0) {
1572
+ chatStore.set(scope, historical);
1573
+ }
1574
+ } catch (error) {
1575
+ callbacksRef.current.onError?.(error);
1576
+ throw error;
1577
+ } finally {
1578
+ setLoadingSessionId((current) => current === sessionId ? void 0 : current);
1579
+ }
1580
+ };
1581
+ const promise = run().finally(() => {
1582
+ if (inFlightLoadRef.current?.sessionId === sessionId) {
1583
+ inFlightLoadRef.current = null;
1584
+ }
1585
+ });
1586
+ inFlightLoadRef.current = { sessionId, promise };
1587
+ return promise;
1588
+ }, []);
2226
1589
  react.useEffect(() => {
2227
- const { userId } = config;
2228
- if (!userId) return;
2229
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1590
+ const key = streamKeyFor(scopeKey, currentSessionId);
1591
+ const unsubscribe = activeStreamStore.subscribe(key, (msgs, isWaiting) => {
2230
1592
  setMessages(msgs);
2231
1593
  setIsWaitingForResponse(isWaiting);
2232
1594
  });
2233
- const active = activeStreamStore.get(userId);
1595
+ const active = activeStreamStore.get(key);
2234
1596
  if (active) {
2235
1597
  setMessages(active.messages);
2236
1598
  setIsWaitingForResponse(active.isWaiting);
2237
1599
  }
2238
1600
  return unsubscribe;
2239
- }, []);
1601
+ }, [scopeKey, currentSessionId]);
2240
1602
  react.useEffect(() => {
2241
- if (!config.userId) return;
2242
1603
  const toSave = messages.filter((m) => !m.isStreaming);
2243
1604
  if (toSave.length > 0) {
2244
- chatStore.set(config.userId, toSave);
1605
+ chatStore.set(scopeKey, toSave);
2245
1606
  }
2246
- }, [messages, config.userId]);
1607
+ }, [messages, scopeKey]);
2247
1608
  react.useEffect(() => {
2248
- const prevUserId = prevUserIdRef.current;
2249
- prevUserIdRef.current = config.userId;
2250
- if (prevUserId === config.userId) return;
2251
- if (prevUserId && !config.userId) {
2252
- chatStore.delete(prevUserId);
2253
- setMessages([]);
2254
- sessionIdRef.current = void 0;
2255
- setIsWaitingForResponse(false);
2256
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
2257
- } else if (config.userId) {
2258
- const stored = chatStore.get(config.userId);
2259
- setMessages(stored);
2260
- sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
2261
- }
2262
- }, [config.userId]);
1609
+ const prevKey = prevScopeKeyRef.current;
1610
+ prevScopeKeyRef.current = scopeKey;
1611
+ if (prevKey === scopeKey) return;
1612
+ const stored = chatStore.get(scopeKey);
1613
+ setMessages(stored);
1614
+ const restoredSessionId = stored.find((m) => m.sessionId)?.sessionId ?? configRef.current.session?.initialId ?? void 0;
1615
+ sessionIdRef.current = restoredSessionId;
1616
+ setCurrentSessionId(restoredSessionId);
1617
+ setIsWaitingForResponse(false);
1618
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1619
+ }, [scopeKey]);
2263
1620
  return {
2264
1621
  messages,
2265
1622
  sendMessage,
@@ -2270,11 +1627,13 @@ function useChatV2(config, callbacks = {}) {
2270
1627
  getSessionId,
2271
1628
  getMessages,
2272
1629
  isWaitingForResponse,
2273
- sessionId: sessionIdRef.current,
1630
+ sessionId: currentSessionId,
2274
1631
  userActionState,
2275
1632
  approveUserAction,
2276
1633
  rejectUserAction,
2277
- resendOtp
1634
+ resendOtp,
1635
+ loadSession,
1636
+ loadingSessionId
2278
1637
  };
2279
1638
  }
2280
1639
  function getSpeechRecognition() {
@@ -2494,15 +1853,18 @@ function useVoice(config = {}, callbacks = {}) {
2494
1853
  }
2495
1854
 
2496
1855
  exports.buildFormattedThinking = buildFormattedThinking;
1856
+ exports.buildScopeKey = buildScopeKey;
2497
1857
  exports.cancelUserAction = cancelUserAction;
2498
1858
  exports.createInitialV2State = createInitialV2State;
2499
1859
  exports.generateId = generateId;
1860
+ exports.listConversations = listConversations;
1861
+ exports.listSessions = listSessions;
2500
1862
  exports.processStreamEventV2 = processStreamEventV2;
2501
1863
  exports.resendUserAction = resendUserAction;
2502
1864
  exports.streamWorkflowEvents = streamWorkflowEvents;
2503
1865
  exports.submitUserAction = submitUserAction;
2504
- exports.useChat = useChat;
2505
1866
  exports.useChatV2 = useChatV2;
2506
1867
  exports.useVoice = useVoice;
1868
+ exports.workingPhaseDetailForDisplay = workingPhaseDetailForDisplay;
2507
1869
  //# sourceMappingURL=index.js.map
2508
1870
  //# sourceMappingURL=index.js.map