@invergent/agent-chat-react 1.6.5 → 1.6.6

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.cjs CHANGED
@@ -8421,10 +8421,16 @@ function applyLlmResponse(state, event) {
8421
8421
  const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
8422
8422
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
8423
8423
  const hasToolCalls = toolCalls.length > 0;
8424
+ const responseToolCallIds = toolCallIdsFromResponse(toolCalls);
8424
8425
  const idx = findLastAssistantIndex(messages);
8425
8426
  const prevAssistant = idx >= 0 ? messages[idx] : void 0;
8426
8427
  const prevHasTools = Boolean(prevAssistant?.toolCalls?.length);
8427
8428
  const hasUserAfter = idx >= 0 && hasUserAfterIndex(messages, idx);
8429
+ const matchesExistingToolTurn = Boolean(
8430
+ hasToolCalls && prevAssistant?.toolCalls?.length && responseToolCallIds.length > 0 && !hasUserAfter && responseToolCallIds.every(
8431
+ (id) => prevAssistant.toolCalls?.some((tc) => tc.id === id)
8432
+ )
8433
+ );
8428
8434
  if (state.hadDeltas && idx >= 0 && !hasUserAfter) {
8429
8435
  const current = messages[idx];
8430
8436
  if (hasToolCalls) {
@@ -8437,6 +8443,13 @@ function applyLlmResponse(state, event) {
8437
8443
  } else {
8438
8444
  messages[idx] = { ...current, status: "complete" };
8439
8445
  }
8446
+ } else if (matchesExistingToolTurn && idx >= 0) {
8447
+ const current = messages[idx];
8448
+ messages[idx] = {
8449
+ ...current,
8450
+ reasoning: responseContent ? appendText(current.reasoning, responseContent) : current.reasoning,
8451
+ status: "streaming"
8452
+ };
8440
8453
  } else if (prevHasTools || !prevAssistant || hasUserAfter) {
8441
8454
  messages.push({
8442
8455
  id: `evt-${event.eventId}`,
@@ -8480,6 +8493,15 @@ function applyLlmResponse(state, event) {
8480
8493
  }
8481
8494
  };
8482
8495
  }
8496
+ function toolCallIdsFromResponse(toolCalls) {
8497
+ return toolCalls.map((toolCall) => stringValue2(objectValue(toolCall)?.id)).filter((id) => id.length > 0);
8498
+ }
8499
+ function appendText(existing, addition) {
8500
+ if (!existing) return addition;
8501
+ if (existing.includes(addition)) return existing;
8502
+ return `${existing}
8503
+ ${addition}`;
8504
+ }
8483
8505
  function applyLlmThinking(state, event) {
8484
8506
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
8485
8507
  const messages = [...state.messages];
@@ -8773,9 +8795,13 @@ function useAgentChatRuntime({
8773
8795
  const streamRef = react.useRef(null);
8774
8796
  const reconnectTimerRef = react.useRef(null);
8775
8797
  const previousSessionIdRef = react.useRef(sessionId);
8798
+ const sessionIdRef = react.useRef(sessionId);
8776
8799
  react.useEffect(() => {
8777
8800
  stateRef.current = state;
8778
8801
  }, [state]);
8802
+ react.useEffect(() => {
8803
+ sessionIdRef.current = sessionId;
8804
+ }, [sessionId]);
8779
8805
  const clearReconnectTimer = react.useCallback(() => {
8780
8806
  if (reconnectTimerRef.current) {
8781
8807
  clearTimeout(reconnectTimerRef.current);
@@ -8787,21 +8813,10 @@ function useAgentChatRuntime({
8787
8813
  streamRef.current = null;
8788
8814
  stream?.close();
8789
8815
  }, []);
8790
- react.useEffect(() => {
8791
- const previousSessionId = previousSessionIdRef.current;
8792
- previousSessionIdRef.current = sessionId;
8793
- clearReconnectTimer();
8794
- closeStream();
8795
- if (!sessionId) {
8796
- setSession(null);
8797
- setState(createInitialAgentChatState());
8798
- return;
8799
- }
8800
- let cancelled = false;
8801
- const connect = (after) => {
8802
- if (cancelled) return;
8816
+ const openStream = react.useCallback(
8817
+ (targetSessionId, after) => {
8803
8818
  const stream = adapter.openEventStream({
8804
- sessionId,
8819
+ sessionId: targetSessionId,
8805
8820
  after: after ?? stateRef.current.lastEventId
8806
8821
  });
8807
8822
  streamRef.current = stream;
@@ -8824,11 +8839,41 @@ function useAgentChatRuntime({
8824
8839
  if (streamRef.current === stream) {
8825
8840
  streamRef.current = null;
8826
8841
  }
8827
- if (!stateRef.current.sessionDone && !cancelled) {
8828
- reconnectTimerRef.current = setTimeout(() => connect(), 3e3);
8842
+ if (!stateRef.current.sessionDone && sessionIdRef.current === targetSessionId) {
8843
+ reconnectTimerRef.current = setTimeout(
8844
+ () => openStream(targetSessionId),
8845
+ 3e3
8846
+ );
8829
8847
  }
8830
8848
  };
8831
- };
8849
+ },
8850
+ [adapter]
8851
+ );
8852
+ const ensureStreamForNewTurn = react.useCallback(
8853
+ (targetSessionId) => {
8854
+ if (streamRef.current && !stateRef.current.sessionDone) return;
8855
+ const after = stateRef.current.lastEventId;
8856
+ clearReconnectTimer();
8857
+ closeStream();
8858
+ stateRef.current = {
8859
+ ...stateRef.current,
8860
+ sessionDone: false,
8861
+ terminal: false
8862
+ };
8863
+ openStream(targetSessionId, after);
8864
+ },
8865
+ [clearReconnectTimer, closeStream, openStream]
8866
+ );
8867
+ react.useEffect(() => {
8868
+ const previousSessionId = previousSessionIdRef.current;
8869
+ previousSessionIdRef.current = sessionId;
8870
+ clearReconnectTimer();
8871
+ closeStream();
8872
+ if (!sessionId) {
8873
+ setSession(null);
8874
+ setState(createInitialAgentChatState());
8875
+ return;
8876
+ }
8832
8877
  const currentState = stateRef.current;
8833
8878
  const preservePendingFirstMessage = previousSessionId === null && currentState.isRunning && currentState.messages.some(
8834
8879
  (message) => message.role === "user" && message.id.startsWith("local-")
@@ -8843,9 +8888,9 @@ function useAgentChatRuntime({
8843
8888
  stateRef.current = initialState;
8844
8889
  setSession(null);
8845
8890
  setState(initialState);
8846
- connect(0);
8891
+ openStream(sessionId, 0);
8847
8892
  adapter.getSession({ sessionId }).then((loadedSession) => {
8848
- if (cancelled) return;
8893
+ if (sessionIdRef.current !== sessionId) return;
8849
8894
  setSession(loadedSession);
8850
8895
  if (loadedSession.messageCount === 0) {
8851
8896
  setState((prev) => ({
@@ -8862,16 +8907,16 @@ function useAgentChatRuntime({
8862
8907
  }
8863
8908
  }).catch(() => void 0);
8864
8909
  return () => {
8865
- cancelled = true;
8866
8910
  clearReconnectTimer();
8867
8911
  closeStream();
8868
8912
  };
8869
- }, [adapter, clearReconnectTimer, closeStream, sessionId]);
8913
+ }, [adapter, clearReconnectTimer, closeStream, openStream, sessionId]);
8870
8914
  const markSending = react.useCallback(
8871
8915
  (content, images) => {
8872
8916
  setState((prev) => ({
8873
8917
  ...prev,
8874
8918
  terminal: false,
8919
+ sessionDone: false,
8875
8920
  isRunning: true,
8876
8921
  messages: [
8877
8922
  ...prev.messages,
@@ -8952,13 +8997,22 @@ function useAgentChatRuntime({
8952
8997
  return;
8953
8998
  }
8954
8999
  try {
9000
+ ensureStreamForNewTurn(sessionId);
8955
9001
  await adapter.sendMessage({ sessionId, content, images });
8956
9002
  } catch (error) {
8957
9003
  markSendError(error instanceof Error ? error.message : "send failed");
8958
9004
  throw error;
8959
9005
  }
8960
9006
  },
8961
- [adapter, agentId, markSendError, markSending, onSessionChange, sessionId]
9007
+ [
9008
+ adapter,
9009
+ agentId,
9010
+ ensureStreamForNewTurn,
9011
+ markSendError,
9012
+ markSending,
9013
+ onSessionChange,
9014
+ sessionId
9015
+ ]
8962
9016
  );
8963
9017
  const stop = react.useCallback(async () => {
8964
9018
  if (!sessionId) return;