@iblai/web-utils 1.7.3 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -802,6 +802,15 @@ const TOOLS = {
802
802
  GOOGLE_SLIDES: "google-slides",
803
803
  GOOGLE_DOCUMENT: "google-docs",
804
804
  };
805
+ // Maps WebSocket tool_call type names to human-readable UI labels
806
+ const TOOL_NAME_MAP = {
807
+ web_search_call: "Searching the web",
808
+ vector_search: "Searching knowledge base",
809
+ calculator: "Calculating",
810
+ file_reader: "Reading file",
811
+ code_executor: "Running code",
812
+ wikipedia: "Searching Wikipedia",
813
+ };
805
814
  const REQUIRED_ACTIONS_FOR_GROUPS = {
806
815
  NOTIFICATIONS: "Ibl.Notifications/Notification/action",
807
816
  };
@@ -1181,17 +1190,19 @@ async function redirectToAuthSpa(options) {
1181
1190
  logoutTimestamp: "ibl_logout_timestamp",
1182
1191
  loginTimestamp: "ibl_login_timestamp",
1183
1192
  tenantSwitching: "ibl_tenant_switching",
1184
- }, hasNonExpiredAuthToken, isOffline, preserveTokenKey, authRedirectProxy, isNativeApp, } = options;
1185
- console.log("[redirectToAuthSpa] starting redirect to auth spa", redirectTo, platformKey, logout, saveRedirect);
1193
+ }, hasNonExpiredAuthToken, isOffline, preserveTokenKey, authRedirectProxy, isNativeApp, forceRedirect = false, } = options;
1194
+ console.log("[redirectToAuthSpa] starting redirect to auth spa", redirectTo, platformKey, logout, saveRedirect, forceRedirect);
1186
1195
  // Skip if a tenant switch is already in progress
1187
- if (cookieNames.tenantSwitching &&
1196
+ if (!forceRedirect &&
1197
+ cookieNames.tenantSwitching &&
1188
1198
  document.cookie.includes(cookieNames.tenantSwitching)) {
1189
1199
  console.log("[redirectToAuthSpa] Tenant switch in progress, skipping redirect");
1190
1200
  return;
1191
1201
  }
1192
1202
  // Skip if a login occurred after the last logout (login takes precedence)
1193
1203
  // but only if this app actually has a valid auth token
1194
- if (hasNonExpiredAuthToken &&
1204
+ if (!forceRedirect &&
1205
+ hasNonExpiredAuthToken &&
1195
1206
  cookieNames.loginTimestamp &&
1196
1207
  cookieNames.logoutTimestamp) {
1197
1208
  const loginTs = getCookieValue(cookieNames.loginTimestamp);
@@ -1203,7 +1214,8 @@ async function redirectToAuthSpa(options) {
1203
1214
  hasValidToken,
1204
1215
  loginAhead: loginTs && logoutTs ? Number(loginTs) > Number(logoutTs) : false,
1205
1216
  });
1206
- if (hasValidToken &&
1217
+ if (!forceRedirect &&
1218
+ hasValidToken &&
1207
1219
  loginTs &&
1208
1220
  logoutTs &&
1209
1221
  Number(loginTs) > Number(logoutTs)) {
@@ -10752,41 +10764,41 @@ async function initServiceWorker(basePath = "") {
10752
10764
  return getServiceWorkerStatus();
10753
10765
  }
10754
10766
 
10755
- const CHECK_NETWORK_STATUS_COMMAND = 'check_network_status';
10767
+ const CHECK_NETWORK_STATUS_COMMAND = "check_network_status";
10756
10768
  // CRITICAL: Eagerly import Tauri API to bundle it inline instead of lazy-loading
10757
10769
  // webpackMode: "eager" prevents creating a separate chunk (7427.js)
10758
10770
  // This ensures the code is in the main bundle and always available offline
10759
10771
  let tauriInvoke = null;
10760
- if (typeof window !== 'undefined' && isTauri()) {
10772
+ if (typeof window !== "undefined" && isTauri()) {
10761
10773
  import(/* webpackMode: "eager" */ '@tauri-apps/api/core')
10762
10774
  .then((tauriCore) => {
10763
10775
  tauriInvoke = tauriCore.invoke;
10764
- console.log('[ServiceWorkerProvider] Tauri API loaded eagerly (bundled inline)');
10776
+ console.log("[ServiceWorkerProvider] Tauri API loaded eagerly (bundled inline)");
10765
10777
  })
10766
10778
  .catch((e) => {
10767
- console.warn('[ServiceWorkerProvider] Failed to load Tauri API:', e);
10779
+ console.warn("[ServiceWorkerProvider] Failed to load Tauri API:", e);
10768
10780
  });
10769
10781
  }
10770
10782
  // localStorage key set by the Tauri offline shell
10771
- const TAURI_OFFLINE_MODE_KEY = 'tauri_offline_mode';
10783
+ const TAURI_OFFLINE_MODE_KEY = "tauri_offline_mode";
10772
10784
  /**
10773
10785
  * Check if the shell has marked us as offline
10774
10786
  */
10775
10787
  function isShellOfflineMode() {
10776
- if (typeof window === 'undefined' ||
10777
- typeof (localStorage === null || localStorage === void 0 ? void 0 : localStorage.getItem) !== 'function')
10788
+ if (typeof window === "undefined" ||
10789
+ typeof (localStorage === null || localStorage === void 0 ? void 0 : localStorage.getItem) !== "function")
10778
10790
  return false;
10779
- return localStorage.getItem(TAURI_OFFLINE_MODE_KEY) === 'true';
10791
+ return localStorage.getItem(TAURI_OFFLINE_MODE_KEY) === "true";
10780
10792
  }
10781
10793
  const ServiceWorkerContext = React.createContext(null);
10782
10794
  function useServiceWorker() {
10783
10795
  const context = React.useContext(ServiceWorkerContext);
10784
10796
  if (!context) {
10785
- throw new Error('useServiceWorker must be used within ServiceWorkerProvider');
10797
+ throw new Error("useServiceWorker must be used within ServiceWorkerProvider");
10786
10798
  }
10787
10799
  return context;
10788
10800
  }
10789
- function ServiceWorkerProvider({ children, basePath = '', }) {
10801
+ function ServiceWorkerProvider({ children, basePath = "", }) {
10790
10802
  // Get initial status, considering the shell's offline flag
10791
10803
  const [status, setStatus] = React.useState(() => {
10792
10804
  const baseStatus = getServiceWorkerStatus();
@@ -10803,7 +10815,7 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10803
10815
  // Skip service worker registration in Tauri offline mode
10804
10816
  // The offline server handles all requests, SW is not needed and will fail to register
10805
10817
  if (isTauriApp && shellOffline) {
10806
- console.log('[ServiceWorkerProvider] Skipping SW registration - Tauri offline mode');
10818
+ console.log("[ServiceWorkerProvider] Skipping SW registration - Tauri offline mode");
10807
10819
  setStatus({
10808
10820
  isSupported: false,
10809
10821
  isRegistered: false,
@@ -10834,11 +10846,11 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10834
10846
  });
10835
10847
  // Subscribe to updates
10836
10848
  const unsubscribeUpdate = onUpdate(() => {
10837
- sonner.toast.info('App update available', {
10838
- description: 'A new version is ready. Click to update.',
10849
+ sonner.toast.info("App update available", {
10850
+ description: "A new version is ready. Click to update.",
10839
10851
  duration: 10000,
10840
10852
  action: {
10841
- label: 'Update',
10853
+ label: "Update",
10842
10854
  onClick: () => {
10843
10855
  skipWaiting();
10844
10856
  },
@@ -10865,7 +10877,7 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10865
10877
  const isOnline = (await tauriInvoke(CHECK_NETWORK_STATUS_COMMAND));
10866
10878
  setStatus((prev) => {
10867
10879
  if (prev.isOnline !== isOnline) {
10868
- console.log('[ServiceWorkerProvider] Network status changed:', isOnline ? 'online' : 'offline');
10880
+ console.log("[ServiceWorkerProvider] Network status changed:", isOnline ? "online" : "offline");
10869
10881
  // Notify service worker of the change
10870
10882
  setOfflineStatus(!isOnline);
10871
10883
  return { ...prev, isOnline };
@@ -10878,10 +10890,10 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10878
10890
  // If we're offline and the Tauri chunk can't load, just skip network checks
10879
10891
  // The error object has a 'name' property for ChunkLoadError
10880
10892
  if (error &&
10881
- typeof error === 'object' &&
10882
- 'name' in error &&
10883
- error.name === 'ChunkLoadError') {
10884
- console.warn('[ServiceWorkerProvider] Tauri API chunk not available (offline), disabling network checks');
10893
+ typeof error === "object" &&
10894
+ "name" in error &&
10895
+ error.name === "ChunkLoadError") {
10896
+ console.warn("[ServiceWorkerProvider] Tauri API chunk not available (offline), disabling network checks");
10885
10897
  // Clear the interval to stop trying
10886
10898
  if (networkCheckInterval) {
10887
10899
  clearInterval(networkCheckInterval);
@@ -10889,7 +10901,7 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10889
10901
  }
10890
10902
  }
10891
10903
  else {
10892
- console.error('[ServiceWorkerProvider] Failed to check network status:', error);
10904
+ console.error("[ServiceWorkerProvider] Failed to check network status:", error);
10893
10905
  }
10894
10906
  }
10895
10907
  };
@@ -10899,7 +10911,7 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10899
10911
  networkCheckInterval = setInterval(checkNetworkStatus, 3000);
10900
10912
  // Also listen to browser offline/online events for immediate feedback
10901
10913
  offlineHandler = () => {
10902
- console.log('[ServiceWorkerProvider] Browser offline event fired');
10914
+ console.log("[ServiceWorkerProvider] Browser offline event fired");
10903
10915
  setStatus((prev) => {
10904
10916
  if (prev.isOnline) {
10905
10917
  setOfflineStatus(true);
@@ -10911,12 +10923,12 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10911
10923
  setTimeout(checkNetworkStatus, 500);
10912
10924
  };
10913
10925
  onlineHandler = () => {
10914
- console.log('[ServiceWorkerProvider] Browser online event fired');
10926
+ console.log("[ServiceWorkerProvider] Browser online event fired");
10915
10927
  // Verify with Tauri before marking as online
10916
10928
  checkNetworkStatus();
10917
10929
  };
10918
- window.addEventListener('offline', offlineHandler);
10919
- window.addEventListener('online', onlineHandler);
10930
+ window.addEventListener("offline", offlineHandler);
10931
+ window.addEventListener("online", onlineHandler);
10920
10932
  }
10921
10933
  return () => {
10922
10934
  unsubscribeUpdate();
@@ -10925,10 +10937,10 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10925
10937
  clearInterval(networkCheckInterval);
10926
10938
  }
10927
10939
  if (offlineHandler) {
10928
- window.removeEventListener('offline', offlineHandler);
10940
+ window.removeEventListener("offline", offlineHandler);
10929
10941
  }
10930
10942
  if (onlineHandler) {
10931
- window.removeEventListener('online', onlineHandler);
10943
+ window.removeEventListener("online", onlineHandler);
10932
10944
  }
10933
10945
  };
10934
10946
  }, [basePath]);
@@ -10937,8 +10949,8 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10937
10949
  }, []);
10938
10950
  const clearCache = React.useCallback(() => {
10939
10951
  clearAllCaches();
10940
- sonner.toast.success('Cache cleared', {
10941
- description: 'All cached data has been cleared.',
10952
+ sonner.toast.success("Cache cleared", {
10953
+ description: "All cached data has been cleared.",
10942
10954
  });
10943
10955
  }, []);
10944
10956
  const refreshCacheStatus = React.useCallback(async () => {
@@ -10970,13 +10982,13 @@ function ServiceWorkerProvider({ children, basePath = '', }) {
10970
10982
  catch (error) {
10971
10983
  // CRITICAL: Catch chunk load errors and fall back to navigator.onLine
10972
10984
  if (error &&
10973
- typeof error === 'object' &&
10974
- 'name' in error &&
10975
- error.name === 'ChunkLoadError') {
10976
- console.warn('[ServiceWorkerProvider] Tauri API chunk not available, using navigator.onLine');
10985
+ typeof error === "object" &&
10986
+ "name" in error &&
10987
+ error.name === "ChunkLoadError") {
10988
+ console.warn("[ServiceWorkerProvider] Tauri API chunk not available, using navigator.onLine");
10977
10989
  return navigator.onLine;
10978
10990
  }
10979
- console.error('[ServiceWorkerProvider] Failed to check network status:', error);
10991
+ console.error("[ServiceWorkerProvider] Failed to check network status:", error);
10980
10992
  return navigator.onLine; // Fallback to browser API
10981
10993
  }
10982
10994
  }, []);
@@ -11003,6 +11015,9 @@ const initialState$5 = {
11003
11015
  currentStreamingMessage: {
11004
11016
  id: "",
11005
11017
  content: "",
11018
+ reasoningContent: "",
11019
+ toolCalls: [],
11020
+ isReasoning: false,
11006
11021
  },
11007
11022
  currentStreamingArtifact: null,
11008
11023
  activeTab: "chat",
@@ -11026,6 +11041,9 @@ const initialState$5 = {
11026
11041
  // Buffering state initialized
11027
11042
  streamingArtifactContentBuffer: "",
11028
11043
  lastArtifactContentFlushTime: 0,
11044
+ // Reasoning buffering state initialized
11045
+ streamingReasoningContentBuffer: "",
11046
+ lastReasoningContentFlushTime: 0,
11029
11047
  metadata: {
11030
11048
  edxCourseId: "",
11031
11049
  edxUsageId: "",
@@ -11055,6 +11073,8 @@ const chatSlice = createSlice({
11055
11073
  content: state.currentStreamingMessage.content,
11056
11074
  timestamp: new Date().toISOString(),
11057
11075
  visible: true,
11076
+ reasoningContent: state.currentStreamingMessage.reasoningContent,
11077
+ toolCalls: state.currentStreamingMessage.toolCalls,
11058
11078
  };
11059
11079
  state.chats = {
11060
11080
  ...state.chats,
@@ -11065,6 +11085,8 @@ const chatSlice = createSlice({
11065
11085
  const temp = {
11066
11086
  ...lastMessageInActiveTabMessages,
11067
11087
  content: state.currentStreamingMessage.content,
11088
+ reasoningContent: state.currentStreamingMessage.reasoningContent,
11089
+ toolCalls: state.currentStreamingMessage.toolCalls,
11068
11090
  };
11069
11091
  state.chats = {
11070
11092
  ...state.chats,
@@ -11103,8 +11125,58 @@ const chatSlice = createSlice({
11103
11125
  setCurrentStreamingMessage: (state, action) => {
11104
11126
  state.currentStreamingMessage = action.payload;
11105
11127
  },
11128
+ // Partial update that only sets id/content without resetting reasoning/tool data
11129
+ setStreamingMessageIdAndContent: (state, action) => {
11130
+ state.currentStreamingMessage.id = action.payload.id;
11131
+ state.currentStreamingMessage.content = action.payload.content;
11132
+ },
11106
11133
  resetCurrentStreamingMessage: (state) => {
11107
- state.currentStreamingMessage = { id: "", content: "" };
11134
+ state.currentStreamingMessage = {
11135
+ id: "",
11136
+ content: "",
11137
+ reasoningContent: "",
11138
+ toolCalls: [],
11139
+ isReasoning: false,
11140
+ };
11141
+ // Reset reasoning buffer as well
11142
+ state.streamingReasoningContentBuffer = "";
11143
+ state.lastReasoningContentFlushTime = 0;
11144
+ },
11145
+ // Buffer reasoning tokens, flush at threshold
11146
+ appendReasoningContent: (state, action) => {
11147
+ // Add to buffer
11148
+ state.streamingReasoningContentBuffer += action.payload;
11149
+ // Check if we should flush (threshold reached)
11150
+ const shouldFlush = state.streamingReasoningContentBuffer.length >=
11151
+ STREAMING_CONTENT_BUFFER_THRESHOLD;
11152
+ if (shouldFlush) {
11153
+ // Flush buffer to actual content
11154
+ state.currentStreamingMessage.reasoningContent +=
11155
+ state.streamingReasoningContentBuffer;
11156
+ state.streamingReasoningContentBuffer = "";
11157
+ state.lastReasoningContentFlushTime = Date.now();
11158
+ }
11159
+ },
11160
+ // Force-flush buffered reasoning to currentStreamingMessage
11161
+ flushReasoningBuffer: (state, _action) => {
11162
+ if (state.streamingReasoningContentBuffer.length > 0) {
11163
+ state.currentStreamingMessage.reasoningContent +=
11164
+ state.streamingReasoningContentBuffer;
11165
+ state.streamingReasoningContentBuffer = "";
11166
+ state.lastReasoningContentFlushTime = Date.now();
11167
+ }
11168
+ },
11169
+ // Set isReasoning flag without touching other fields
11170
+ setIsReasoning: (state, action) => {
11171
+ state.currentStreamingMessage.isReasoning = action.payload;
11172
+ },
11173
+ // Sets isReasoning = false (triggers UI collapse)
11174
+ setReasoningComplete: (state, _action) => {
11175
+ state.currentStreamingMessage.isReasoning = false;
11176
+ },
11177
+ // Appends tool call entry
11178
+ addToolCall: (state, action) => {
11179
+ state.currentStreamingMessage.toolCalls.push(action.payload);
11108
11180
  },
11109
11181
  updateStreamingMessageId: (state, action) => {
11110
11182
  const oldId = state.currentStreamingMessage.id;
@@ -11262,11 +11334,15 @@ const chatSlice = createSlice({
11262
11334
  }
11263
11335
  const activeMessages = state.chats[state.activeTab];
11264
11336
  const lastMessage = activeMessages[activeMessages.length - 1];
11337
+ const toolCalls = state.currentStreamingMessage.toolCalls || [];
11338
+ const reasoningContent = state.currentStreamingMessage.reasoningContent || "";
11265
11339
  if (!lastMessage || lastMessage.id !== streamingId) {
11266
11340
  const temp = {
11267
11341
  id: streamingId,
11268
11342
  role: "assistant",
11269
11343
  content: state.currentStreamingMessage.content,
11344
+ reasoningContent,
11345
+ toolCalls,
11270
11346
  timestamp: new Date().toISOString(),
11271
11347
  visible: true,
11272
11348
  };
@@ -11275,6 +11351,19 @@ const chatSlice = createSlice({
11275
11351
  [state.activeTab]: [...activeMessages, temp],
11276
11352
  };
11277
11353
  }
11354
+ else {
11355
+ const updatedMessages = [...activeMessages];
11356
+ updatedMessages[activeMessages.length - 1] = {
11357
+ ...lastMessage,
11358
+ content: state.currentStreamingMessage.content,
11359
+ reasoningContent,
11360
+ toolCalls,
11361
+ };
11362
+ state.chats = {
11363
+ ...state.chats,
11364
+ [state.activeTab]: updatedMessages,
11365
+ };
11366
+ }
11278
11367
  },
11279
11368
  upsertStreamingArtifactVersionOnLastMessage: (state, action) => {
11280
11369
  const { artifactId, title, content, fileExtension, sessionId, versionNumber = 1, username, metadata, isPartial, } = action.payload;
@@ -11477,6 +11566,16 @@ const selectStreamingArtifactFullContent = (state) => {
11477
11566
  return null;
11478
11567
  return artifact.content + buffer;
11479
11568
  };
11569
+ // NEW: Get the full streaming reasoning content including buffer
11570
+ const selectStreamingReasoningContent = (state) => {
11571
+ const reasoningContent = state.chatSliceShared.currentStreamingMessage.reasoningContent;
11572
+ const buffer = state.chatSliceShared.streamingReasoningContentBuffer;
11573
+ return reasoningContent + buffer;
11574
+ };
11575
+ // NEW: Get isReasoning flag
11576
+ const selectIsReasoning = (state) => state.chatSliceShared.currentStreamingMessage.isReasoning;
11577
+ // NEW: Get streaming tool calls
11578
+ const selectStreamingToolCalls = (state) => state.chatSliceShared.currentStreamingMessage.toolCalls;
11480
11579
 
11481
11580
  class TimeTracker {
11482
11581
  constructor(config) {
@@ -12262,6 +12361,8 @@ const useChat = ({ wsUrl, wsToken, flowConfig, sessionId, stopGenerationWsUrl, e
12262
12361
  const currentStreamingMessage = React.useRef({
12263
12362
  id: null,
12264
12363
  content: "",
12364
+ reasoningContent: "",
12365
+ toolCalls: [],
12265
12366
  });
12266
12367
  // Track artifact state during streaming
12267
12368
  const currentArtifact = React.useRef(null);
@@ -12275,6 +12376,14 @@ const useChat = ({ wsUrl, wsToken, flowConfig, sessionId, stopGenerationWsUrl, e
12275
12376
  lastFlushTime: 0,
12276
12377
  flushTimer: null,
12277
12378
  });
12379
+ // NEW: Content buffer for batching reasoning streaming updates
12380
+ const reasoningContentBuffer = React.useRef({
12381
+ content: "",
12382
+ lastFlushTime: 0,
12383
+ flushTimer: null,
12384
+ });
12385
+ // NEW: Track if we've seen reasoning tokens to detect transition to response
12386
+ const hasSeenReasoning = React.useRef(false);
12278
12387
  const stopGenerationSocket = React.useRef(null);
12279
12388
  const isInitialConnection = React.useRef(true);
12280
12389
  const connectionAttempts = React.useRef(0);
@@ -12955,6 +13064,22 @@ const useChat = ({ wsUrl, wsToken, flowConfig, sessionId, stopGenerationWsUrl, e
12955
13064
  }
12956
13065
  return;
12957
13066
  }
13067
+ // NEW: Handle top-level tool_call messages (separate message type)
13068
+ if (messageData.type === "tool_call" && messageData.value) {
13069
+ const toolCallValue = messageData.value;
13070
+ // Dispatch tool call to Redux
13071
+ dispatch(chatActions.addToolCall({
13072
+ id: toolCallValue.id || "",
13073
+ name: toolCallValue.name || "",
13074
+ input: toolCallValue.tool_input,
13075
+ log: toolCallValue.log || "",
13076
+ result: toolCallValue.result || "",
13077
+ }));
13078
+ // Ensure message exists in array and update it with latest data
13079
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13080
+ // Early return since tool_call messages don't have data/eos fields
13081
+ return;
13082
+ }
12958
13083
  // Handle start of new streaming session
12959
13084
  if (messageData.generation_id &&
12960
13085
  !messageData.data &&
@@ -12967,6 +13092,21 @@ const useChat = ({ wsUrl, wsToken, flowConfig, sessionId, stopGenerationWsUrl, e
12967
13092
  id: messageData.generation_id,
12968
13093
  content: "",
12969
13094
  };
13095
+ // Reset Redux streaming message with all fields
13096
+ dispatch(chatActions.setCurrentStreamingMessage({
13097
+ id: messageData.generation_id,
13098
+ content: "",
13099
+ reasoningContent: "",
13100
+ toolCalls: [],
13101
+ isReasoning: false,
13102
+ }));
13103
+ // NEW: Reset reasoning refs and buffer for new generation
13104
+ hasSeenReasoning.current = false;
13105
+ reasoningContentBuffer.current = {
13106
+ content: "",
13107
+ lastFlushTime: 0,
13108
+ flushTimer: null,
13109
+ };
12970
13110
  onStreamingMessageUpdate === null || onStreamingMessageUpdate === void 0 ? void 0 : onStreamingMessageUpdate(currentStreamingMessage.current);
12971
13111
  onStatusChange("streaming");
12972
13112
  onStreamingChange(true);
@@ -12989,7 +13129,69 @@ const useChat = ({ wsUrl, wsToken, flowConfig, sessionId, stopGenerationWsUrl, e
12989
13129
  // Handle streaming message content (chat text)
12990
13130
  if ((messageData === null || messageData === void 0 ? void 0 : messageData.type) !== "chat_messages" &&
12991
13131
  (messageData.data !== undefined || messageData.eos !== undefined)) {
13132
+ // NEW: Handle annotations (reasoning tokens) BEFORE processing data
13133
+ if (messageData.annotations &&
13134
+ Array.isArray(messageData.annotations)) {
13135
+ for (const annotation of messageData.annotations) {
13136
+ if (annotation.type === "reasoning" && annotation.text) {
13137
+ // Mark that we've seen reasoning
13138
+ hasSeenReasoning.current = true;
13139
+ // Set isReasoning to true without overwriting toolCalls
13140
+ dispatch(chatActions.setIsReasoning(true));
13141
+ // Ensure message exists in array immediately so reasoning section appears
13142
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13143
+ // Buffer reasoning content
13144
+ reasoningContentBuffer.current.content += annotation.text;
13145
+ // Check if we should flush (threshold or timer)
13146
+ const shouldFlush = reasoningContentBuffer.current.content.length >=
13147
+ STREAMING_CONTENT_BUFFER_THRESHOLD;
13148
+ const now = Date.now();
13149
+ const timeSinceLastFlush = now - reasoningContentBuffer.current.lastFlushTime;
13150
+ if (shouldFlush ||
13151
+ timeSinceLastFlush >= STREAMING_CONTENT_FLUSH_INTERVAL) {
13152
+ // Flush buffer to Redux
13153
+ dispatch(chatActions.appendReasoningContent(reasoningContentBuffer.current.content));
13154
+ reasoningContentBuffer.current.content = "";
13155
+ reasoningContentBuffer.current.lastFlushTime = now;
13156
+ // Ensure message exists in array and update it with latest data
13157
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13158
+ }
13159
+ else {
13160
+ // Set up timer to flush if not already set
13161
+ if (!reasoningContentBuffer.current.flushTimer) {
13162
+ reasoningContentBuffer.current.flushTimer = setTimeout(() => {
13163
+ if (reasoningContentBuffer.current.content.length > 0) {
13164
+ dispatch(chatActions.appendReasoningContent(reasoningContentBuffer.current.content));
13165
+ reasoningContentBuffer.current.content = "";
13166
+ reasoningContentBuffer.current.lastFlushTime =
13167
+ Date.now();
13168
+ // Ensure message exists in array and update it with latest data
13169
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13170
+ }
13171
+ reasoningContentBuffer.current.flushTimer = null;
13172
+ }, STREAMING_CONTENT_FLUSH_INTERVAL);
13173
+ }
13174
+ }
13175
+ }
13176
+ }
13177
+ }
12992
13178
  const messageText = messageData.data || "";
13179
+ // NEW: Detect reasoning→response transition (first non-empty data after reasoning)
13180
+ if (messageText && hasSeenReasoning.current) {
13181
+ // Flush remaining reasoning buffer
13182
+ if (reasoningContentBuffer.current.content.length > 0) {
13183
+ dispatch(chatActions.appendReasoningContent(reasoningContentBuffer.current.content));
13184
+ reasoningContentBuffer.current.content = "";
13185
+ }
13186
+ // Flush reasoning buffer in Redux
13187
+ dispatch(chatActions.flushReasoningBuffer(undefined));
13188
+ // Mark reasoning as complete
13189
+ dispatch(chatActions.setReasoningComplete(undefined));
13190
+ // Update message in array with final reasoning state
13191
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13192
+ // Reset flag so we only do this once
13193
+ hasSeenReasoning.current = false;
13194
+ }
12993
13195
  // If we have content to add, update the in-memory streaming message and Redux
12994
13196
  if (messageText && currentStreamingMessage.current) {
12995
13197
  console.log("[ws-message] Streaming content chunk:", {
@@ -13003,13 +13205,28 @@ const useChat = ({ wsUrl, wsToken, flowConfig, sessionId, stopGenerationWsUrl, e
13003
13205
  content: currentStreamingMessage.current.content + messageText,
13004
13206
  };
13005
13207
  onStreamingMessageUpdate === null || onStreamingMessageUpdate === void 0 ? void 0 : onStreamingMessageUpdate(currentStreamingMessage.current);
13006
- // Update or add message in Redux store
13208
+ // Update or add message in Redux store - use ensureStreamingAssistantMessage for consistency
13007
13209
  if ((_w = currentStreamingMessage.current) === null || _w === void 0 ? void 0 : _w.id) {
13008
- // TODO: Investigate why `appendMessageToActiveTab` is consuming this action payload
13009
- dispatch(chatActions.appendMessageToActiveTab(undefined));
13210
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13010
13211
  }
13011
13212
  }
13012
13213
  if (messageData.eos) {
13214
+ // NEW: Flush any remaining reasoning buffer on EOS
13215
+ if (reasoningContentBuffer.current.content.length > 0) {
13216
+ dispatch(chatActions.appendReasoningContent(reasoningContentBuffer.current.content));
13217
+ reasoningContentBuffer.current.content = "";
13218
+ }
13219
+ // Flush reasoning buffer in Redux
13220
+ dispatch(chatActions.flushReasoningBuffer(undefined));
13221
+ // Mark reasoning as complete
13222
+ dispatch(chatActions.setReasoningComplete(undefined));
13223
+ // Update message in array with final state
13224
+ dispatch(chatActions.ensureStreamingAssistantMessage(undefined));
13225
+ // Clear any pending flush timer
13226
+ if (reasoningContentBuffer.current.flushTimer) {
13227
+ clearTimeout(reasoningContentBuffer.current.flushTimer);
13228
+ reasoningContentBuffer.current.flushTimer = null;
13229
+ }
13013
13230
  // Handle end of stream
13014
13231
  const hasPendingArtifact = !!pendingArtifactVersion.current;
13015
13232
  const hasMessageContent = !!((_x = currentStreamingMessage.current) === null || _x === void 0 ? void 0 : _x.content) &&
@@ -13624,7 +13841,13 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13624
13841
  dispatch(chatActions.setStatus(status));
13625
13842
  };
13626
13843
  const onStreamingMessageUpdate = (message) => {
13627
- dispatch(chatActions.setCurrentStreamingMessage(message));
13844
+ // Use setStreamingMessageIdAndContent to avoid resetting reasoning/tool state
13845
+ if (message.id) {
13846
+ dispatch(chatActions.setStreamingMessageIdAndContent({
13847
+ id: message.id,
13848
+ content: message.content,
13849
+ }));
13850
+ }
13628
13851
  };
13629
13852
  const { sendMessage, stopGenerating, ws, isConnected, messageQueue, resetConnection, } = useChat({
13630
13853
  wsUrl,
@@ -13706,7 +13929,7 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13706
13929
  try {
13707
13930
  previousChats =
13708
13931
  ((_d = (_c = data === null || data === void 0 ? void 0 : data.data) === null || _c === void 0 ? void 0 : _c.results) === null || _d === void 0 ? void 0 : _d.map((result) => {
13709
- var _a, _b, _c;
13932
+ var _a, _b, _c, _d, _e, _f;
13710
13933
  const artifactVersions = (_a = result.artifact_versions) === null || _a === void 0 ? void 0 : _a.map((av) => {
13711
13934
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
13712
13935
  return ({
@@ -13745,6 +13968,30 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13745
13968
  fileSize: file.file_size,
13746
13969
  uploadUrl: file.url,
13747
13970
  }))) || [];
13971
+ // Extract additional_kwargs - check both direct and nested paths
13972
+ const additionalKwargs = result.additional_kwargs ||
13973
+ ((_d = (_c = result.message) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.additional_kwargs);
13974
+ // Extract reasoning content from additional_kwargs
13975
+ const reasoningSummaries = (_e = additionalKwargs === null || additionalKwargs === void 0 ? void 0 : additionalKwargs.reasoning) === null || _e === void 0 ? void 0 : _e.summary;
13976
+ const reasoningContent = Array.isArray(reasoningSummaries)
13977
+ ? reasoningSummaries
13978
+ .map((s) => s.text)
13979
+ .filter(Boolean)
13980
+ .join("\n\n")
13981
+ : undefined;
13982
+ // Extract tool calls from additional_kwargs
13983
+ const toolOutputs = additionalKwargs === null || additionalKwargs === void 0 ? void 0 : additionalKwargs.tool_outputs;
13984
+ const toolCalls = Array.isArray(toolOutputs)
13985
+ ? toolOutputs.map((tool) => {
13986
+ var _a;
13987
+ return ({
13988
+ id: tool.id || "",
13989
+ name: tool.type || "",
13990
+ log: ((_a = tool.action) === null || _a === void 0 ? void 0 : _a.query) || "",
13991
+ result: tool.status || "",
13992
+ });
13993
+ })
13994
+ : undefined;
13748
13995
  return {
13749
13996
  ...result,
13750
13997
  role: result.type === "human" ? "user" : "assistant",
@@ -13752,17 +13999,45 @@ function useAdvancedChat({ tenantKey, mentorId, username = ANONYMOUS_USERNAME, t
13752
13999
  id: result.id || new Date().getTime(),
13753
14000
  content: typeof result.content === "object" &&
13754
14001
  result.content.length > 0
13755
- ? (_c = result.content.find((item) => item.text)) === null || _c === void 0 ? void 0 : _c.text
14002
+ ? (_f = result.content.find((item) => item.text)) === null || _f === void 0 ? void 0 : _f.text
13756
14003
  : result.content,
13757
14004
  // Add transformed artifact versions
13758
14005
  artifactVersions: artifactVersions || undefined,
13759
14006
  fileAttachments: fileAttachments.length > 0 ? fileAttachments : undefined,
14007
+ reasoningContent: reasoningContent || undefined,
14008
+ toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
13760
14009
  };
13761
14010
  }).reverse()) || [];
13762
14011
  }
13763
14012
  catch (error) {
13764
14013
  console.error(JSON.stringify(error));
13765
14014
  }
14015
+ // Preserve reasoningContent and toolCalls from existing messages
14016
+ // when the session API re-fetch doesn't include additional_kwargs.
14017
+ const existingMessages = chats[activeTab] || [];
14018
+ if (existingMessages.length > 0 && previousChats.length > 0) {
14019
+ const existingByContent = new Map();
14020
+ for (const m of existingMessages) {
14021
+ if (m.reasoningContent || (m.toolCalls && m.toolCalls.length > 0)) {
14022
+ existingByContent.set(m.content, m);
14023
+ }
14024
+ }
14025
+ if (existingByContent.size > 0) {
14026
+ for (const msg of previousChats) {
14027
+ const existing = existingByContent.get(msg.content);
14028
+ if (existing) {
14029
+ if (!msg.reasoningContent && existing.reasoningContent) {
14030
+ msg.reasoningContent = existing.reasoningContent;
14031
+ }
14032
+ if ((!msg.toolCalls || msg.toolCalls.length === 0) &&
14033
+ existing.toolCalls &&
14034
+ existing.toolCalls.length > 0) {
14035
+ msg.toolCalls = existing.toolCalls;
14036
+ }
14037
+ }
14038
+ }
14039
+ }
14040
+ }
13766
14041
  dispatch(chatActions.setNewMessages(previousChats));
13767
14042
  }
13768
14043
  catch (error) {
@@ -17940,6 +18215,7 @@ exports.ServiceWorkerProvider = ServiceWorkerProvider;
17940
18215
  exports.SubscriptionFlow = SubscriptionFlow;
17941
18216
  exports.SubscriptionFlowV2 = SubscriptionFlowV2;
17942
18217
  exports.TOOLS = TOOLS;
18218
+ exports.TOOL_NAME_MAP = TOOL_NAME_MAP;
17943
18219
  exports.TenantContext = TenantContext;
17944
18220
  exports.TenantContextProvider = TenantContextProvider;
17945
18221
  exports.TenantProvider = TenantProvider;
@@ -18044,6 +18320,7 @@ exports.selectEnableChatActionsPopup = selectEnableChatActionsPopup;
18044
18320
  exports.selectIframeContext = selectIframeContext;
18045
18321
  exports.selectIsError = selectIsError;
18046
18322
  exports.selectIsPending = selectIsPending;
18323
+ exports.selectIsReasoning = selectIsReasoning;
18047
18324
  exports.selectIsStopped = selectIsStopped;
18048
18325
  exports.selectIsTyping = selectIsTyping;
18049
18326
  exports.selectLastArtifactContentFlushTime = selectLastArtifactContentFlushTime;
@@ -18058,6 +18335,8 @@ exports.selectStatus = selectStatus;
18058
18335
  exports.selectStreaming = selectStreaming;
18059
18336
  exports.selectStreamingArtifactContentBuffer = selectStreamingArtifactContentBuffer;
18060
18337
  exports.selectStreamingArtifactFullContent = selectStreamingArtifactFullContent;
18338
+ exports.selectStreamingReasoningContent = selectStreamingReasoningContent;
18339
+ exports.selectStreamingToolCalls = selectStreamingToolCalls;
18061
18340
  exports.selectToken = selectToken;
18062
18341
  exports.selectTokenEnabled = selectTokenEnabled;
18063
18342
  exports.selectTools = selectTools;