@ai-sdk/langchain 2.0.255 → 2.0.257
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 +15 -0
- package/dist/index.js +137 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +137 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +17 -3
- package/src/types.ts +4 -0
- package/src/utils.ts +177 -22
package/src/adapter.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
extractReasoningFromContentBlocks,
|
|
21
21
|
extractCitationsFromContentBlocks,
|
|
22
22
|
emitSourceChunks,
|
|
23
|
+
getLangGraphProviderMetadata,
|
|
23
24
|
} from './utils';
|
|
24
25
|
import { type LangGraphEventState } from './types';
|
|
25
26
|
import { type StreamCallbacks } from './stream-callbacks';
|
|
@@ -432,6 +433,7 @@ export function toUIMessageStream<TState = unknown>(
|
|
|
432
433
|
*/
|
|
433
434
|
const langGraphState: LangGraphEventState = {
|
|
434
435
|
messageSeen: new Map(),
|
|
436
|
+
messageNamespaces: new Map(),
|
|
435
437
|
messageConcat: new Map(),
|
|
436
438
|
emittedToolCalls: new Set<string>(),
|
|
437
439
|
emittedImages: new Set<string>(),
|
|
@@ -439,6 +441,7 @@ export function toUIMessageStream<TState = unknown>(
|
|
|
439
441
|
messageReasoningIds: new Map(),
|
|
440
442
|
toolCallInfoByIndex: new Map(),
|
|
441
443
|
currentStep: null as number | null,
|
|
444
|
+
stepNamespace: null,
|
|
442
445
|
emittedToolCallsByKey: new Map<string, string>(),
|
|
443
446
|
emittedSourceIds: new Set<string>(),
|
|
444
447
|
};
|
|
@@ -542,7 +545,7 @@ export function toUIMessageStream<TState = unknown>(
|
|
|
542
545
|
);
|
|
543
546
|
} else {
|
|
544
547
|
const eventArray = value as unknown[];
|
|
545
|
-
const [type, data] = parseLangGraphEvent(eventArray);
|
|
548
|
+
const [, type, data] = parseLangGraphEvent(eventArray);
|
|
546
549
|
|
|
547
550
|
if (type === 'values') {
|
|
548
551
|
lastValuesData = data as TState;
|
|
@@ -583,11 +586,22 @@ export function toUIMessageStream<TState = unknown>(
|
|
|
583
586
|
* where the values handler never ran to emit *-end events.
|
|
584
587
|
*/
|
|
585
588
|
for (const [id, seen] of langGraphState.messageSeen) {
|
|
589
|
+
const providerMetadata = getLangGraphProviderMetadata(
|
|
590
|
+
langGraphState.messageNamespaces.get(id),
|
|
591
|
+
);
|
|
586
592
|
if (seen.text) {
|
|
587
|
-
controller.enqueue({
|
|
593
|
+
controller.enqueue({
|
|
594
|
+
type: 'text-end',
|
|
595
|
+
id,
|
|
596
|
+
...(providerMetadata !== undefined && { providerMetadata }),
|
|
597
|
+
});
|
|
588
598
|
}
|
|
589
599
|
if (seen.reasoning) {
|
|
590
|
-
controller.enqueue({
|
|
600
|
+
controller.enqueue({
|
|
601
|
+
type: 'reasoning-end',
|
|
602
|
+
id,
|
|
603
|
+
...(providerMetadata !== undefined && { providerMetadata }),
|
|
604
|
+
});
|
|
591
605
|
}
|
|
592
606
|
}
|
|
593
607
|
|
package/src/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface LangGraphMessageSeen {
|
|
|
12
12
|
export interface LangGraphEventState {
|
|
13
13
|
/** Tracks which message IDs have been seen */
|
|
14
14
|
messageSeen: Map<string, LangGraphMessageSeen>;
|
|
15
|
+
/** Maps message IDs to the LangGraph namespace that emitted them */
|
|
16
|
+
messageNamespaces: Map<string, string[]>;
|
|
15
17
|
/** Accumulates message chunks for later reference */
|
|
16
18
|
messageConcat: Map<string, AIMessageChunk>;
|
|
17
19
|
/** Tracks which tool call IDs have emitted tool-input-start */
|
|
@@ -26,6 +28,8 @@ export interface LangGraphEventState {
|
|
|
26
28
|
toolCallInfoByIndex: Map<string, Map<number, { id: string; name: string }>>;
|
|
27
29
|
/** Tracks the current LangGraph step for start-step/finish-step events */
|
|
28
30
|
currentStep: number | null;
|
|
31
|
+
/** Namespace whose step counter drives the global UI step lifecycle */
|
|
32
|
+
stepNamespace: string | null;
|
|
29
33
|
/** Maps tool call key (name:argsJson) to tool call ID for HITL interrupt handling */
|
|
30
34
|
emittedToolCallsByKey: Map<string, string>;
|
|
31
35
|
/** Tracks source IDs already emitted to avoid duplicates across messages/values events */
|
package/src/utils.ts
CHANGED
|
@@ -29,16 +29,104 @@ import {
|
|
|
29
29
|
} from './types';
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Parses a LangGraph event tuple into [type, data].
|
|
32
|
+
* Parses a LangGraph event tuple into [namespace, type, data].
|
|
33
33
|
* Handles both 2-element [type, data] and 3-element [namespace, type, data] formats.
|
|
34
34
|
*
|
|
35
35
|
* @param event - The raw LangGraph event array.
|
|
36
|
-
* @returns A tuple of [type, data].
|
|
36
|
+
* @returns A tuple of [namespace, type, data].
|
|
37
37
|
*/
|
|
38
38
|
export function parseLangGraphEvent(
|
|
39
39
|
event: unknown[],
|
|
40
|
-
): [type: unknown, data: unknown] {
|
|
41
|
-
return event.length === 3
|
|
40
|
+
): [namespace: unknown | undefined, type: unknown, data: unknown] {
|
|
41
|
+
return event.length === 3
|
|
42
|
+
? [event[0], event[1], event[2]]
|
|
43
|
+
: [undefined, event[0], event[1]];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getLangGraphProviderMetadata(
|
|
47
|
+
namespace: string[] | undefined,
|
|
48
|
+
): ProviderMetadata | undefined {
|
|
49
|
+
return namespace === undefined
|
|
50
|
+
? undefined
|
|
51
|
+
: {
|
|
52
|
+
langchain: {
|
|
53
|
+
namespace,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addLangGraphNamespace(
|
|
59
|
+
chunk: UIMessageChunk,
|
|
60
|
+
namespace: string[] | undefined,
|
|
61
|
+
): UIMessageChunk {
|
|
62
|
+
if (namespace === undefined) {
|
|
63
|
+
return chunk;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
switch (chunk.type) {
|
|
67
|
+
case 'text-start':
|
|
68
|
+
case 'text-delta':
|
|
69
|
+
case 'text-end':
|
|
70
|
+
case 'reasoning-start':
|
|
71
|
+
case 'reasoning-delta':
|
|
72
|
+
case 'reasoning-end':
|
|
73
|
+
case 'tool-input-start':
|
|
74
|
+
case 'tool-input-available':
|
|
75
|
+
case 'tool-input-error':
|
|
76
|
+
case 'tool-output-available':
|
|
77
|
+
case 'tool-output-error':
|
|
78
|
+
case 'source-url':
|
|
79
|
+
case 'source-document':
|
|
80
|
+
case 'file': {
|
|
81
|
+
return {
|
|
82
|
+
...chunk,
|
|
83
|
+
providerMetadata: {
|
|
84
|
+
...chunk.providerMetadata,
|
|
85
|
+
langchain: {
|
|
86
|
+
...chunk.providerMetadata?.langchain,
|
|
87
|
+
namespace,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
return chunk;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createLangGraphNamespaceController(
|
|
98
|
+
controller: ReadableStreamDefaultController<UIMessageChunk>,
|
|
99
|
+
state: LangGraphEventState,
|
|
100
|
+
eventNamespace: string[] | undefined,
|
|
101
|
+
): ReadableStreamDefaultController<UIMessageChunk> {
|
|
102
|
+
return {
|
|
103
|
+
get desiredSize() {
|
|
104
|
+
return controller.desiredSize;
|
|
105
|
+
},
|
|
106
|
+
close: () => controller.close(),
|
|
107
|
+
error: reason => controller.error(reason),
|
|
108
|
+
enqueue: chunk => {
|
|
109
|
+
if (chunk === undefined) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let messageNamespace: string[] | undefined;
|
|
114
|
+
switch (chunk.type) {
|
|
115
|
+
case 'text-start':
|
|
116
|
+
case 'text-delta':
|
|
117
|
+
case 'text-end':
|
|
118
|
+
case 'reasoning-start':
|
|
119
|
+
case 'reasoning-delta':
|
|
120
|
+
case 'reasoning-end':
|
|
121
|
+
messageNamespace = state.messageNamespaces.get(chunk.id);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
controller.enqueue(
|
|
126
|
+
addLangGraphNamespace(chunk, messageNamespace ?? eventNamespace),
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
42
130
|
}
|
|
43
131
|
|
|
44
132
|
/**
|
|
@@ -1077,6 +1165,53 @@ function getOrCreateToolCallInfoByIndex(
|
|
|
1077
1165
|
return toolCallInfo;
|
|
1078
1166
|
}
|
|
1079
1167
|
|
|
1168
|
+
/**
|
|
1169
|
+
* Normalizes legacy tuples without a namespace and explicit empty root
|
|
1170
|
+
* namespaces to the same key.
|
|
1171
|
+
*/
|
|
1172
|
+
function getLangGraphNamespaceKey(namespace: string[] | undefined): string {
|
|
1173
|
+
return JSON.stringify(namespace ?? []);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Ends and clears message state for the namespace that drives the global UI
|
|
1178
|
+
* step lifecycle. Returns whether another namespace still has active text or
|
|
1179
|
+
* reasoning that would be invalidated by a global finish-step chunk.
|
|
1180
|
+
*/
|
|
1181
|
+
function closeStepNamespaceMessages(
|
|
1182
|
+
state: LangGraphEventState,
|
|
1183
|
+
stepNamespace: string,
|
|
1184
|
+
controller: ReadableStreamDefaultController<UIMessageChunk>,
|
|
1185
|
+
): boolean {
|
|
1186
|
+
let hasConcurrentMessageParts = false;
|
|
1187
|
+
|
|
1188
|
+
for (const [id, seen] of state.messageSeen) {
|
|
1189
|
+
const messageNamespace = getLangGraphNamespaceKey(
|
|
1190
|
+
state.messageNamespaces.get(id),
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
if (messageNamespace !== stepNamespace) {
|
|
1194
|
+
if (seen.text || seen.reasoning) {
|
|
1195
|
+
hasConcurrentMessageParts = true;
|
|
1196
|
+
}
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
if (seen.text) {
|
|
1201
|
+
controller.enqueue({ type: 'text-end', id });
|
|
1202
|
+
}
|
|
1203
|
+
if (seen.reasoning) {
|
|
1204
|
+
controller.enqueue({ type: 'reasoning-end', id });
|
|
1205
|
+
}
|
|
1206
|
+
state.messageSeen.delete(id);
|
|
1207
|
+
state.messageNamespaces.delete(id);
|
|
1208
|
+
state.messageConcat.delete(id);
|
|
1209
|
+
state.messageReasoningIds.delete(id);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
return hasConcurrentMessageParts;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1080
1215
|
/**
|
|
1081
1216
|
* Processes a LangGraph event and emits UI message chunks.
|
|
1082
1217
|
*
|
|
@@ -1099,7 +1234,13 @@ export function processLangGraphEvent(
|
|
|
1099
1234
|
toolCallInfoByIndex,
|
|
1100
1235
|
emittedToolCallsByKey,
|
|
1101
1236
|
} = state;
|
|
1102
|
-
const [type, data] = parseLangGraphEvent(event);
|
|
1237
|
+
const [rawNamespace, type, data] = parseLangGraphEvent(event);
|
|
1238
|
+
const namespace =
|
|
1239
|
+
Array.isArray(rawNamespace) &&
|
|
1240
|
+
rawNamespace.every(segment => typeof segment === 'string')
|
|
1241
|
+
? rawNamespace
|
|
1242
|
+
: undefined;
|
|
1243
|
+
controller = createLangGraphNamespaceController(controller, state, namespace);
|
|
1103
1244
|
|
|
1104
1245
|
switch (type) {
|
|
1105
1246
|
case 'custom': {
|
|
@@ -1147,32 +1288,45 @@ export function processLangGraphEvent(
|
|
|
1147
1288
|
|
|
1148
1289
|
if (!msgId) return;
|
|
1149
1290
|
|
|
1291
|
+
if (namespace !== undefined) {
|
|
1292
|
+
state.messageNamespaces.set(msgId, namespace);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1150
1295
|
/**
|
|
1151
|
-
*
|
|
1152
|
-
*
|
|
1153
|
-
* the
|
|
1154
|
-
*
|
|
1296
|
+
* The first namespace with a step counter drives the global UI step
|
|
1297
|
+
* lifecycle. This is the root namespace for complete subgraph streams and
|
|
1298
|
+
* the selected namespace for streams filtered to one subgraph.
|
|
1299
|
+
*
|
|
1300
|
+
* Other namespaces have independent counters and must not change this
|
|
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.
|
|
1155
1303
|
*/
|
|
1156
1304
|
const langgraphStep =
|
|
1157
1305
|
typeof metadata?.langgraph_step === 'number'
|
|
1158
1306
|
? metadata.langgraph_step
|
|
1159
1307
|
: null;
|
|
1160
|
-
|
|
1308
|
+
const eventNamespace = getLangGraphNamespaceKey(namespace);
|
|
1309
|
+
if (langgraphStep !== null && state.stepNamespace === null) {
|
|
1310
|
+
state.stepNamespace = eventNamespace;
|
|
1311
|
+
}
|
|
1312
|
+
if (
|
|
1313
|
+
langgraphStep !== null &&
|
|
1314
|
+
eventNamespace === state.stepNamespace &&
|
|
1315
|
+
langgraphStep !== state.currentStep
|
|
1316
|
+
) {
|
|
1161
1317
|
if (state.currentStep !== null) {
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
messageConcat.delete(id);
|
|
1171
|
-
messageReasoningIds.delete(id);
|
|
1318
|
+
const hasConcurrentMessageParts = closeStepNamespaceMessages(
|
|
1319
|
+
state,
|
|
1320
|
+
state.stepNamespace,
|
|
1321
|
+
controller,
|
|
1322
|
+
);
|
|
1323
|
+
if (!hasConcurrentMessageParts) {
|
|
1324
|
+
controller.enqueue({ type: 'finish-step' });
|
|
1325
|
+
controller.enqueue({ type: 'start-step' });
|
|
1172
1326
|
}
|
|
1173
|
-
|
|
1327
|
+
} else {
|
|
1328
|
+
controller.enqueue({ type: 'start-step' });
|
|
1174
1329
|
}
|
|
1175
|
-
controller.enqueue({ type: 'start-step' });
|
|
1176
1330
|
state.currentStep = langgraphStep;
|
|
1177
1331
|
}
|
|
1178
1332
|
|
|
@@ -1470,6 +1624,7 @@ export function processLangGraphEvent(
|
|
|
1470
1624
|
}
|
|
1471
1625
|
|
|
1472
1626
|
messageSeen.delete(id);
|
|
1627
|
+
state.messageNamespaces.delete(id);
|
|
1473
1628
|
messageConcat.delete(id);
|
|
1474
1629
|
messageReasoningIds.delete(id);
|
|
1475
1630
|
}
|