@ai-sdk/langchain 2.0.267 → 2.0.268
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/CHANGELOG.md +10 -0
- package/dist/index.js +102 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +102 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +4 -3
- package/src/types.ts +6 -4
- package/src/utils.ts +134 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/langchain",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.268",
|
|
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.
|
|
28
|
+
"ai": "6.0.260"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@langchain/core": "^1.1.5",
|
package/src/adapter.ts
CHANGED
|
@@ -435,13 +435,14 @@ export function toUIMessageStream<TState = unknown>(
|
|
|
435
435
|
messageSeen: new Map(),
|
|
436
436
|
messageNamespaces: new Map(),
|
|
437
437
|
messageConcat: new Map(),
|
|
438
|
+
messageIdsInCurrentStepByNamespace: new Map(),
|
|
438
439
|
emittedToolCalls: new Set<string>(),
|
|
440
|
+
emittedToolCallsInCurrentStepByNamespace: new Map(),
|
|
439
441
|
emittedImages: new Set<string>(),
|
|
440
442
|
emittedReasoningIds: new Set<string>(),
|
|
441
443
|
messageReasoningIds: new Map(),
|
|
442
444
|
toolCallInfoByIndex: new Map(),
|
|
443
|
-
|
|
444
|
-
stepNamespace: null,
|
|
445
|
+
currentStepsByNamespace: new Map(),
|
|
445
446
|
emittedToolCallsByKey: new Map<string, string>(),
|
|
446
447
|
emittedSourceIds: new Set<string>(),
|
|
447
448
|
};
|
|
@@ -608,7 +609,7 @@ export function toUIMessageStream<TState = unknown>(
|
|
|
608
609
|
/**
|
|
609
610
|
* Emit finish-step if a step was started
|
|
610
611
|
*/
|
|
611
|
-
if (langGraphState.
|
|
612
|
+
if (langGraphState.currentStepsByNamespace.size > 0) {
|
|
612
613
|
controller.enqueue({ type: 'finish-step' });
|
|
613
614
|
}
|
|
614
615
|
controller.enqueue({ type: 'finish' });
|
package/src/types.ts
CHANGED
|
@@ -16,8 +16,12 @@ export interface LangGraphEventState {
|
|
|
16
16
|
messageNamespaces: Map<string, string[]>;
|
|
17
17
|
/** Accumulates message chunks for later reference */
|
|
18
18
|
messageConcat: Map<string, AIMessageChunk>;
|
|
19
|
+
/** Tracks message IDs observed in each namespace's current LangGraph step */
|
|
20
|
+
messageIdsInCurrentStepByNamespace: Map<string, Set<string>>;
|
|
19
21
|
/** Tracks which tool call IDs have emitted tool-input-start */
|
|
20
22
|
emittedToolCalls: Set<string>;
|
|
23
|
+
/** Tracks tool-input-start chunks emitted in each namespace's current step */
|
|
24
|
+
emittedToolCallsInCurrentStepByNamespace: Map<string, Set<string>>;
|
|
21
25
|
/** Maps image IDs to their message IDs (for chunks that don't include the ID) */
|
|
22
26
|
emittedImages: Set<string>;
|
|
23
27
|
/** Maps reasoning block IDs to their message IDs (for chunks that don't include the ID) */
|
|
@@ -26,10 +30,8 @@ export interface LangGraphEventState {
|
|
|
26
30
|
messageReasoningIds: Map<string, string>;
|
|
27
31
|
/** Maps message ID + tool call index to tool call info (for streaming chunks without ID) */
|
|
28
32
|
toolCallInfoByIndex: Map<string, Map<number, { id: string; name: string }>>;
|
|
29
|
-
/** Tracks
|
|
30
|
-
|
|
31
|
-
/** Namespace whose step counter drives the global UI step lifecycle */
|
|
32
|
-
stepNamespace: string | null;
|
|
33
|
+
/** Tracks each namespace's current LangGraph step */
|
|
34
|
+
currentStepsByNamespace: Map<string, number>;
|
|
33
35
|
/** Maps tool call key (name:argsJson) to tool call ID for HITL interrupt handling */
|
|
34
36
|
emittedToolCallsByKey: Map<string, string>;
|
|
35
37
|
/** Tracks source IDs already emitted to avoid duplicates across messages/values events */
|
package/src/utils.ts
CHANGED
|
@@ -1173,10 +1173,63 @@ function getLangGraphNamespaceKey(namespace: string[] | undefined): string {
|
|
|
1173
1173
|
return JSON.stringify(namespace ?? []);
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
+
function getOrCreateNamespaceSet(
|
|
1177
|
+
setsByNamespace: Map<string, Set<string>>,
|
|
1178
|
+
namespace: string,
|
|
1179
|
+
): Set<string> {
|
|
1180
|
+
let values = setsByNamespace.get(namespace);
|
|
1181
|
+
if (!values) {
|
|
1182
|
+
values = new Set();
|
|
1183
|
+
setsByNamespace.set(namespace, values);
|
|
1184
|
+
}
|
|
1185
|
+
return values;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function hasEmittedToolCallInCurrentStep(
|
|
1189
|
+
state: LangGraphEventState,
|
|
1190
|
+
toolCallId: string,
|
|
1191
|
+
namespace: string,
|
|
1192
|
+
): boolean {
|
|
1193
|
+
return !state.currentStepsByNamespace.has(namespace)
|
|
1194
|
+
? state.emittedToolCalls.has(toolCallId)
|
|
1195
|
+
: state.emittedToolCallsInCurrentStepByNamespace
|
|
1196
|
+
.get(namespace)
|
|
1197
|
+
?.has(toolCallId) === true;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function markToolCallEmitted(
|
|
1201
|
+
state: LangGraphEventState,
|
|
1202
|
+
toolCallId: string,
|
|
1203
|
+
namespace: string,
|
|
1204
|
+
): void {
|
|
1205
|
+
state.emittedToolCalls.add(toolCallId);
|
|
1206
|
+
if (state.currentStepsByNamespace.has(namespace)) {
|
|
1207
|
+
getOrCreateNamespaceSet(
|
|
1208
|
+
state.emittedToolCallsInCurrentStepByNamespace,
|
|
1209
|
+
namespace,
|
|
1210
|
+
).add(toolCallId);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function findMessageCurrentStepNamespace(
|
|
1215
|
+
state: LangGraphEventState,
|
|
1216
|
+
messageId: string,
|
|
1217
|
+
): string | undefined {
|
|
1218
|
+
for (const [
|
|
1219
|
+
namespace,
|
|
1220
|
+
messageIds,
|
|
1221
|
+
] of state.messageIdsInCurrentStepByNamespace) {
|
|
1222
|
+
if (messageIds.has(messageId)) {
|
|
1223
|
+
return namespace;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return undefined;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1176
1229
|
/**
|
|
1177
|
-
* Ends and clears message state for
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1230
|
+
* Ends and clears message state for an advancing namespace. Returns whether
|
|
1231
|
+
* another namespace still has active text or reasoning that would be
|
|
1232
|
+
* invalidated by a global finish-step chunk.
|
|
1180
1233
|
*/
|
|
1181
1234
|
function closeStepNamespaceMessages(
|
|
1182
1235
|
state: LangGraphEventState,
|
|
@@ -1240,6 +1293,7 @@ export function processLangGraphEvent(
|
|
|
1240
1293
|
rawNamespace.every(segment => typeof segment === 'string')
|
|
1241
1294
|
? rawNamespace
|
|
1242
1295
|
: undefined;
|
|
1296
|
+
const eventNamespace = getLangGraphNamespaceKey(namespace);
|
|
1243
1297
|
controller = createLangGraphNamespaceController(controller, state, namespace);
|
|
1244
1298
|
|
|
1245
1299
|
switch (type) {
|
|
@@ -1293,41 +1347,65 @@ export function processLangGraphEvent(
|
|
|
1293
1347
|
}
|
|
1294
1348
|
|
|
1295
1349
|
/**
|
|
1296
|
-
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
1301
|
-
* cursor. A finish-step chunk clears every active UI text/reasoning part,
|
|
1302
|
-
* so suppress the global boundary when another namespace is still active.
|
|
1350
|
+
* Each namespace has an independent LangGraph step counter. Advancing a
|
|
1351
|
+
* namespace starts a new reducer scope so provider-scoped tool call IDs
|
|
1352
|
+
* can be reused in that namespace. A finish-step chunk clears every
|
|
1353
|
+
* active UI text/reasoning part, so omit it when another namespace is
|
|
1354
|
+
* still active.
|
|
1303
1355
|
*/
|
|
1304
1356
|
const langgraphStep =
|
|
1305
1357
|
typeof metadata?.langgraph_step === 'number'
|
|
1306
1358
|
? metadata.langgraph_step
|
|
1307
1359
|
: null;
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1360
|
+
if (langgraphStep !== null) {
|
|
1361
|
+
const currentStep =
|
|
1362
|
+
state.currentStepsByNamespace.get(eventNamespace) ?? null;
|
|
1363
|
+
if (currentStep === null) {
|
|
1364
|
+
if (state.currentStepsByNamespace.size === 0) {
|
|
1365
|
+
controller.enqueue({ type: 'start-step' });
|
|
1366
|
+
}
|
|
1367
|
+
state.currentStepsByNamespace.set(eventNamespace, langgraphStep);
|
|
1368
|
+
state.messageIdsInCurrentStepByNamespace.set(
|
|
1369
|
+
eventNamespace,
|
|
1370
|
+
new Set(),
|
|
1371
|
+
);
|
|
1372
|
+
state.emittedToolCallsInCurrentStepByNamespace.set(
|
|
1373
|
+
eventNamespace,
|
|
1374
|
+
new Set(),
|
|
1375
|
+
);
|
|
1376
|
+
} else if (langgraphStep !== currentStep) {
|
|
1318
1377
|
const hasConcurrentMessageParts = closeStepNamespaceMessages(
|
|
1319
1378
|
state,
|
|
1320
|
-
|
|
1379
|
+
eventNamespace,
|
|
1321
1380
|
controller,
|
|
1322
1381
|
);
|
|
1323
1382
|
if (!hasConcurrentMessageParts) {
|
|
1324
1383
|
controller.enqueue({ type: 'finish-step' });
|
|
1325
|
-
controller.enqueue({ type: 'start-step' });
|
|
1326
1384
|
}
|
|
1327
|
-
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* A concurrent namespace prevents finish-step because it would close
|
|
1388
|
+
* that namespace's active text/reasoning parts. Still emit start-step
|
|
1389
|
+
* so tool calls in the advancing namespace have a distinct reducer
|
|
1390
|
+
* scope while the concurrent message lifecycle remains active.
|
|
1391
|
+
*/
|
|
1328
1392
|
controller.enqueue({ type: 'start-step' });
|
|
1393
|
+
state.currentStepsByNamespace.set(eventNamespace, langgraphStep);
|
|
1394
|
+
state.messageIdsInCurrentStepByNamespace.set(
|
|
1395
|
+
eventNamespace,
|
|
1396
|
+
new Set(),
|
|
1397
|
+
);
|
|
1398
|
+
state.emittedToolCallsInCurrentStepByNamespace.set(
|
|
1399
|
+
eventNamespace,
|
|
1400
|
+
new Set(),
|
|
1401
|
+
);
|
|
1329
1402
|
}
|
|
1330
|
-
|
|
1403
|
+
}
|
|
1404
|
+
if (state.currentStepsByNamespace.has(eventNamespace)) {
|
|
1405
|
+
getOrCreateNamespaceSet(
|
|
1406
|
+
state.messageIdsInCurrentStepByNamespace,
|
|
1407
|
+
eventNamespace,
|
|
1408
|
+
).add(msgId);
|
|
1331
1409
|
}
|
|
1332
1410
|
|
|
1333
1411
|
/**
|
|
@@ -1451,8 +1529,14 @@ export function processLangGraphEvent(
|
|
|
1451
1529
|
updatedSeen.tool ??= new Set();
|
|
1452
1530
|
updatedSeen.tool.add(toolCallId);
|
|
1453
1531
|
|
|
1454
|
-
if (
|
|
1455
|
-
|
|
1532
|
+
if (
|
|
1533
|
+
!hasEmittedToolCallInCurrentStep(
|
|
1534
|
+
state,
|
|
1535
|
+
toolCallId,
|
|
1536
|
+
eventNamespace,
|
|
1537
|
+
)
|
|
1538
|
+
) {
|
|
1539
|
+
markToolCallEmitted(state, toolCallId, eventNamespace);
|
|
1456
1540
|
controller.enqueue({
|
|
1457
1541
|
type: 'tool-input-start',
|
|
1458
1542
|
toolCallId: toolCallId,
|
|
@@ -1595,6 +1679,9 @@ export function processLangGraphEvent(
|
|
|
1595
1679
|
* Finalize all pending message chunks
|
|
1596
1680
|
*/
|
|
1597
1681
|
for (const [id, seen] of messageSeen) {
|
|
1682
|
+
const messageNamespace = getLangGraphNamespaceKey(
|
|
1683
|
+
state.messageNamespaces.get(id),
|
|
1684
|
+
);
|
|
1598
1685
|
if (seen.text) controller.enqueue({ type: 'text-end', id });
|
|
1599
1686
|
if (seen.tool) {
|
|
1600
1687
|
for (const toolCallId of seen.tool) {
|
|
@@ -1604,7 +1691,7 @@ export function processLangGraphEvent(
|
|
|
1604
1691
|
);
|
|
1605
1692
|
|
|
1606
1693
|
if (toolCall) {
|
|
1607
|
-
|
|
1694
|
+
markToolCallEmitted(state, toolCallId, messageNamespace);
|
|
1608
1695
|
// Store mapping for HITL interrupt lookup
|
|
1609
1696
|
const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;
|
|
1610
1697
|
emittedToolCallsByKey.set(toolCallKey, toolCallId);
|
|
@@ -1751,18 +1838,30 @@ export function processLangGraphEvent(
|
|
|
1751
1838
|
|
|
1752
1839
|
if (toolCalls && toolCalls.length > 0) {
|
|
1753
1840
|
for (const toolCall of toolCalls) {
|
|
1841
|
+
const messageNamespace = findMessageCurrentStepNamespace(
|
|
1842
|
+
state,
|
|
1843
|
+
msgId,
|
|
1844
|
+
);
|
|
1845
|
+
const lifecycleNamespace = messageNamespace ?? eventNamespace;
|
|
1846
|
+
const wasObservedInCurrentStep = messageNamespace !== undefined;
|
|
1754
1847
|
/**
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1848
|
+
* Emit tool calls recovered from a message in the current step,
|
|
1849
|
+
* even when a prior step used the same provider-scoped ID.
|
|
1850
|
+
* Otherwise, preserve stream-wide suppression for historical
|
|
1851
|
+
* completed calls and values-only streams without step metadata.
|
|
1759
1852
|
*/
|
|
1760
1853
|
if (
|
|
1761
1854
|
toolCall.id &&
|
|
1762
|
-
!
|
|
1763
|
-
|
|
1855
|
+
!hasEmittedToolCallInCurrentStep(
|
|
1856
|
+
state,
|
|
1857
|
+
toolCall.id,
|
|
1858
|
+
lifecycleNamespace,
|
|
1859
|
+
) &&
|
|
1860
|
+
(wasObservedInCurrentStep ||
|
|
1861
|
+
(!emittedToolCalls.has(toolCall.id) &&
|
|
1862
|
+
!completedToolCallIds.has(toolCall.id)))
|
|
1764
1863
|
) {
|
|
1765
|
-
|
|
1864
|
+
markToolCallEmitted(state, toolCall.id, lifecycleNamespace);
|
|
1766
1865
|
// Store mapping for HITL interrupt lookup
|
|
1767
1866
|
const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;
|
|
1768
1867
|
emittedToolCallsByKey.set(toolCallKey, toolCall.id);
|
|
@@ -1902,7 +2001,7 @@ export function processLangGraphEvent(
|
|
|
1902
2001
|
* so the UI knows what tool is being called with proper lifecycle
|
|
1903
2002
|
*/
|
|
1904
2003
|
if (!emittedToolCalls.has(toolCallId)) {
|
|
1905
|
-
|
|
2004
|
+
markToolCallEmitted(state, toolCallId, eventNamespace);
|
|
1906
2005
|
emittedToolCallsByKey.set(toolCallKey, toolCallId);
|
|
1907
2006
|
controller.enqueue({
|
|
1908
2007
|
type: 'tool-input-start',
|