@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.js CHANGED
@@ -8119,10 +8119,16 @@ function applyLlmResponse(state, event) {
8119
8119
  const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
8120
8120
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
8121
8121
  const hasToolCalls = toolCalls.length > 0;
8122
+ const responseToolCallIds = toolCallIdsFromResponse(toolCalls);
8122
8123
  const idx = findLastAssistantIndex(messages);
8123
8124
  const prevAssistant = idx >= 0 ? messages[idx] : void 0;
8124
8125
  const prevHasTools = Boolean(prevAssistant?.toolCalls?.length);
8125
8126
  const hasUserAfter = idx >= 0 && hasUserAfterIndex(messages, idx);
8127
+ const matchesExistingToolTurn = Boolean(
8128
+ hasToolCalls && prevAssistant?.toolCalls?.length && responseToolCallIds.length > 0 && !hasUserAfter && responseToolCallIds.every(
8129
+ (id) => prevAssistant.toolCalls?.some((tc) => tc.id === id)
8130
+ )
8131
+ );
8126
8132
  if (state.hadDeltas && idx >= 0 && !hasUserAfter) {
8127
8133
  const current = messages[idx];
8128
8134
  if (hasToolCalls) {
@@ -8135,6 +8141,13 @@ function applyLlmResponse(state, event) {
8135
8141
  } else {
8136
8142
  messages[idx] = { ...current, status: "complete" };
8137
8143
  }
8144
+ } else if (matchesExistingToolTurn && idx >= 0) {
8145
+ const current = messages[idx];
8146
+ messages[idx] = {
8147
+ ...current,
8148
+ reasoning: responseContent ? appendText(current.reasoning, responseContent) : current.reasoning,
8149
+ status: "streaming"
8150
+ };
8138
8151
  } else if (prevHasTools || !prevAssistant || hasUserAfter) {
8139
8152
  messages.push({
8140
8153
  id: `evt-${event.eventId}`,
@@ -8178,6 +8191,15 @@ function applyLlmResponse(state, event) {
8178
8191
  }
8179
8192
  };
8180
8193
  }
8194
+ function toolCallIdsFromResponse(toolCalls) {
8195
+ return toolCalls.map((toolCall) => stringValue2(objectValue(toolCall)?.id)).filter((id) => id.length > 0);
8196
+ }
8197
+ function appendText(existing, addition) {
8198
+ if (!existing) return addition;
8199
+ if (existing.includes(addition)) return existing;
8200
+ return `${existing}
8201
+ ${addition}`;
8202
+ }
8181
8203
  function applyLlmThinking(state, event) {
8182
8204
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
8183
8205
  const messages = [...state.messages];
@@ -8471,9 +8493,13 @@ function useAgentChatRuntime({
8471
8493
  const streamRef = useRef(null);
8472
8494
  const reconnectTimerRef = useRef(null);
8473
8495
  const previousSessionIdRef = useRef(sessionId);
8496
+ const sessionIdRef = useRef(sessionId);
8474
8497
  useEffect(() => {
8475
8498
  stateRef.current = state;
8476
8499
  }, [state]);
8500
+ useEffect(() => {
8501
+ sessionIdRef.current = sessionId;
8502
+ }, [sessionId]);
8477
8503
  const clearReconnectTimer = useCallback(() => {
8478
8504
  if (reconnectTimerRef.current) {
8479
8505
  clearTimeout(reconnectTimerRef.current);
@@ -8485,21 +8511,10 @@ function useAgentChatRuntime({
8485
8511
  streamRef.current = null;
8486
8512
  stream?.close();
8487
8513
  }, []);
8488
- useEffect(() => {
8489
- const previousSessionId = previousSessionIdRef.current;
8490
- previousSessionIdRef.current = sessionId;
8491
- clearReconnectTimer();
8492
- closeStream();
8493
- if (!sessionId) {
8494
- setSession(null);
8495
- setState(createInitialAgentChatState());
8496
- return;
8497
- }
8498
- let cancelled = false;
8499
- const connect = (after) => {
8500
- if (cancelled) return;
8514
+ const openStream = useCallback(
8515
+ (targetSessionId, after) => {
8501
8516
  const stream = adapter.openEventStream({
8502
- sessionId,
8517
+ sessionId: targetSessionId,
8503
8518
  after: after ?? stateRef.current.lastEventId
8504
8519
  });
8505
8520
  streamRef.current = stream;
@@ -8522,11 +8537,41 @@ function useAgentChatRuntime({
8522
8537
  if (streamRef.current === stream) {
8523
8538
  streamRef.current = null;
8524
8539
  }
8525
- if (!stateRef.current.sessionDone && !cancelled) {
8526
- reconnectTimerRef.current = setTimeout(() => connect(), 3e3);
8540
+ if (!stateRef.current.sessionDone && sessionIdRef.current === targetSessionId) {
8541
+ reconnectTimerRef.current = setTimeout(
8542
+ () => openStream(targetSessionId),
8543
+ 3e3
8544
+ );
8527
8545
  }
8528
8546
  };
8529
- };
8547
+ },
8548
+ [adapter]
8549
+ );
8550
+ const ensureStreamForNewTurn = useCallback(
8551
+ (targetSessionId) => {
8552
+ if (streamRef.current && !stateRef.current.sessionDone) return;
8553
+ const after = stateRef.current.lastEventId;
8554
+ clearReconnectTimer();
8555
+ closeStream();
8556
+ stateRef.current = {
8557
+ ...stateRef.current,
8558
+ sessionDone: false,
8559
+ terminal: false
8560
+ };
8561
+ openStream(targetSessionId, after);
8562
+ },
8563
+ [clearReconnectTimer, closeStream, openStream]
8564
+ );
8565
+ useEffect(() => {
8566
+ const previousSessionId = previousSessionIdRef.current;
8567
+ previousSessionIdRef.current = sessionId;
8568
+ clearReconnectTimer();
8569
+ closeStream();
8570
+ if (!sessionId) {
8571
+ setSession(null);
8572
+ setState(createInitialAgentChatState());
8573
+ return;
8574
+ }
8530
8575
  const currentState = stateRef.current;
8531
8576
  const preservePendingFirstMessage = previousSessionId === null && currentState.isRunning && currentState.messages.some(
8532
8577
  (message) => message.role === "user" && message.id.startsWith("local-")
@@ -8541,9 +8586,9 @@ function useAgentChatRuntime({
8541
8586
  stateRef.current = initialState;
8542
8587
  setSession(null);
8543
8588
  setState(initialState);
8544
- connect(0);
8589
+ openStream(sessionId, 0);
8545
8590
  adapter.getSession({ sessionId }).then((loadedSession) => {
8546
- if (cancelled) return;
8591
+ if (sessionIdRef.current !== sessionId) return;
8547
8592
  setSession(loadedSession);
8548
8593
  if (loadedSession.messageCount === 0) {
8549
8594
  setState((prev) => ({
@@ -8560,16 +8605,16 @@ function useAgentChatRuntime({
8560
8605
  }
8561
8606
  }).catch(() => void 0);
8562
8607
  return () => {
8563
- cancelled = true;
8564
8608
  clearReconnectTimer();
8565
8609
  closeStream();
8566
8610
  };
8567
- }, [adapter, clearReconnectTimer, closeStream, sessionId]);
8611
+ }, [adapter, clearReconnectTimer, closeStream, openStream, sessionId]);
8568
8612
  const markSending = useCallback(
8569
8613
  (content, images) => {
8570
8614
  setState((prev) => ({
8571
8615
  ...prev,
8572
8616
  terminal: false,
8617
+ sessionDone: false,
8573
8618
  isRunning: true,
8574
8619
  messages: [
8575
8620
  ...prev.messages,
@@ -8650,13 +8695,22 @@ function useAgentChatRuntime({
8650
8695
  return;
8651
8696
  }
8652
8697
  try {
8698
+ ensureStreamForNewTurn(sessionId);
8653
8699
  await adapter.sendMessage({ sessionId, content, images });
8654
8700
  } catch (error) {
8655
8701
  markSendError(error instanceof Error ? error.message : "send failed");
8656
8702
  throw error;
8657
8703
  }
8658
8704
  },
8659
- [adapter, agentId, markSendError, markSending, onSessionChange, sessionId]
8705
+ [
8706
+ adapter,
8707
+ agentId,
8708
+ ensureStreamForNewTurn,
8709
+ markSendError,
8710
+ markSending,
8711
+ onSessionChange,
8712
+ sessionId
8713
+ ]
8660
8714
  );
8661
8715
  const stop = useCallback(async () => {
8662
8716
  if (!sessionId) return;