@ai-sdk/langchain 2.0.300 → 2.0.302

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/langchain",
3
- "version": "2.0.300",
3
+ "version": "2.0.302",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "ai": "6.0.292"
28
+ "ai": "6.0.294"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@langchain/core": "^1.1.5",
@@ -33,8 +33,8 @@
33
33
  "@types/node": "20.17.24",
34
34
  "tsup": "^8",
35
35
  "typescript": "5.8.3",
36
- "@ai-sdk/provider-utils": "4.0.54",
37
- "@vercel/ai-tsconfig": "0.0.0"
36
+ "@vercel/ai-tsconfig": "0.0.0",
37
+ "@ai-sdk/provider-utils": "4.0.56"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@langchain/core": ">=1.0.0",
package/src/adapter.ts CHANGED
@@ -438,6 +438,7 @@ export function toUIMessageStream<TState = unknown>(
438
438
  messageIdsInCurrentStepByNamespace: new Map(),
439
439
  emittedToolCalls: new Set<string>(),
440
440
  emittedToolCallsInCurrentStepByNamespace: new Map(),
441
+ unfinishedToolCallsByNamespace: new Map(),
441
442
  emittedImages: new Set<string>(),
442
443
  emittedReasoningIds: new Set<string>(),
443
444
  messageReasoningIds: new Map(),
package/src/types.ts CHANGED
@@ -22,6 +22,8 @@ export interface LangGraphEventState {
22
22
  emittedToolCalls: Set<string>;
23
23
  /** Tracks tool-input-start chunks emitted in each namespace's current step */
24
24
  emittedToolCallsInCurrentStepByNamespace: Map<string, Set<string>>;
25
+ /** Tracks tool calls still awaiting a terminal output in each namespace */
26
+ unfinishedToolCallsByNamespace: Map<string, Set<string>>;
25
27
  /** Maps image IDs to their message IDs (for chunks that don't include the ID) */
26
28
  emittedImages: Set<string>;
27
29
  /** Maps reasoning block IDs to their message IDs (for chunks that don't include the ID) */
package/src/utils.ts CHANGED
@@ -1211,6 +1211,27 @@ function markToolCallEmitted(
1211
1211
  }
1212
1212
  }
1213
1213
 
1214
+ function markToolCallStarted(
1215
+ state: LangGraphEventState,
1216
+ toolCallId: string,
1217
+ namespace: string,
1218
+ ): void {
1219
+ markToolCallEmitted(state, toolCallId, namespace);
1220
+ getOrCreateNamespaceSet(state.unfinishedToolCallsByNamespace, namespace).add(
1221
+ toolCallId,
1222
+ );
1223
+ }
1224
+
1225
+ function hasUnfinishedToolCall(
1226
+ state: LangGraphEventState,
1227
+ toolCallId: string,
1228
+ namespace: string,
1229
+ ): boolean {
1230
+ return (
1231
+ state.unfinishedToolCallsByNamespace.get(namespace)?.has(toolCallId) ===
1232
+ true
1233
+ );
1234
+ }
1214
1235
  function findMessageCurrentStepNamespace(
1215
1236
  state: LangGraphEventState,
1216
1237
  messageId: string,
@@ -1353,6 +1374,7 @@ export function processLangGraphEvent(
1353
1374
  * active UI text/reasoning part, so omit it when another namespace is
1354
1375
  * still active.
1355
1376
  */
1377
+ let startedUIReducerStep = false;
1356
1378
  const langgraphStep =
1357
1379
  typeof metadata?.langgraph_step === 'number'
1358
1380
  ? metadata.langgraph_step
@@ -1363,6 +1385,7 @@ export function processLangGraphEvent(
1363
1385
  if (currentStep === null) {
1364
1386
  if (state.currentStepsByNamespace.size === 0) {
1365
1387
  controller.enqueue({ type: 'start-step' });
1388
+ startedUIReducerStep = true;
1366
1389
  }
1367
1390
  state.currentStepsByNamespace.set(eventNamespace, langgraphStep);
1368
1391
  state.messageIdsInCurrentStepByNamespace.set(
@@ -1390,6 +1413,7 @@ export function processLangGraphEvent(
1390
1413
  * scope while the concurrent message lifecycle remains active.
1391
1414
  */
1392
1415
  controller.enqueue({ type: 'start-step' });
1416
+ startedUIReducerStep = true;
1393
1417
  state.currentStepsByNamespace.set(eventNamespace, langgraphStep);
1394
1418
  state.messageIdsInCurrentStepByNamespace.set(
1395
1419
  eventNamespace,
@@ -1536,7 +1560,7 @@ export function processLangGraphEvent(
1536
1560
  eventNamespace,
1537
1561
  )
1538
1562
  ) {
1539
- markToolCallEmitted(state, toolCallId, eventNamespace);
1563
+ markToolCallStarted(state, toolCallId, eventNamespace);
1540
1564
  controller.enqueue({
1541
1565
  type: 'tool-input-start',
1542
1566
  toolCallId: toolCallId,
@@ -1650,6 +1674,48 @@ export function processLangGraphEvent(
1650
1674
  const status = dataSource.status as string | undefined;
1651
1675
 
1652
1676
  if (toolCallId) {
1677
+ const wasEmittedInCurrentStep = hasEmittedToolCallInCurrentStep(
1678
+ state,
1679
+ toolCallId,
1680
+ eventNamespace,
1681
+ );
1682
+ const isDelayedOutputForPreviousLifecycle =
1683
+ !wasEmittedInCurrentStep &&
1684
+ hasUnfinishedToolCall(state, toolCallId, eventNamespace);
1685
+
1686
+ if (
1687
+ !wasEmittedInCurrentStep &&
1688
+ !isDelayedOutputForPreviousLifecycle
1689
+ ) {
1690
+ /**
1691
+ * A newly observed namespace does not normally start another global
1692
+ * reducer step while another namespace is active. Reused provider
1693
+ * IDs need a distinct reducer scope, however, or the new output
1694
+ * overwrites the prior namespace's tool part.
1695
+ */
1696
+ if (
1697
+ !startedUIReducerStep &&
1698
+ state.currentStepsByNamespace.has(eventNamespace) &&
1699
+ emittedToolCalls.has(toolCallId)
1700
+ ) {
1701
+ controller.enqueue({ type: 'start-step' });
1702
+ }
1703
+
1704
+ markToolCallStarted(state, toolCallId, eventNamespace);
1705
+ controller.enqueue({
1706
+ type: 'tool-input-start',
1707
+ toolCallId,
1708
+ toolName:
1709
+ typeof dataSource.name === 'string'
1710
+ ? dataSource.name
1711
+ : 'unknown',
1712
+ dynamic: true,
1713
+ });
1714
+ }
1715
+
1716
+ state.unfinishedToolCallsByNamespace
1717
+ .get(eventNamespace)
1718
+ ?.delete(toolCallId);
1653
1719
  if (status === 'error') {
1654
1720
  // Tool execution failed
1655
1721
  controller.enqueue({
@@ -1861,7 +1927,7 @@ export function processLangGraphEvent(
1861
1927
  (!emittedToolCalls.has(toolCall.id) &&
1862
1928
  !completedToolCallIds.has(toolCall.id)))
1863
1929
  ) {
1864
- markToolCallEmitted(state, toolCall.id, lifecycleNamespace);
1930
+ markToolCallStarted(state, toolCall.id, lifecycleNamespace);
1865
1931
  // Store mapping for HITL interrupt lookup
1866
1932
  const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;
1867
1933
  emittedToolCallsByKey.set(toolCallKey, toolCall.id);
@@ -2001,7 +2067,7 @@ export function processLangGraphEvent(
2001
2067
  * so the UI knows what tool is being called with proper lifecycle
2002
2068
  */
2003
2069
  if (!emittedToolCalls.has(toolCallId)) {
2004
- markToolCallEmitted(state, toolCallId, eventNamespace);
2070
+ markToolCallStarted(state, toolCallId, eventNamespace);
2005
2071
  emittedToolCallsByKey.set(toolCallKey, toolCallId);
2006
2072
  controller.enqueue({
2007
2073
  type: 'tool-input-start',