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