@ai-sdk/langchain 2.0.210 → 2.0.212
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 +13 -0
- package/dist/index.js +75 -51
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +75 -51
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +5 -11
- package/src/types.ts +11 -11
- package/src/utils.ts +95 -54
package/src/utils.ts
CHANGED
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
type UserContent,
|
|
17
17
|
} from 'ai';
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
import type {
|
|
20
|
+
LangGraphEventState,
|
|
21
|
+
LangGraphMessageSeen,
|
|
22
|
+
ReasoningContentBlock,
|
|
23
|
+
ThinkingContentBlock,
|
|
24
|
+
GPT5ReasoningOutput,
|
|
25
|
+
ImageGenerationOutput,
|
|
25
26
|
} from './types';
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -954,6 +955,38 @@ export function extractImageOutputs(
|
|
|
954
955
|
return toolOutputs.filter(isImageGenerationOutput);
|
|
955
956
|
}
|
|
956
957
|
|
|
958
|
+
/**
|
|
959
|
+
* Returns per-message bookkeeping, creating it on first use.
|
|
960
|
+
* Map keys keep remote-controlled message IDs like "__proto__" from touching Object.prototype.
|
|
961
|
+
*/
|
|
962
|
+
function getOrCreateMessageSeen(
|
|
963
|
+
messageSeen: LangGraphEventState['messageSeen'],
|
|
964
|
+
msgId: string,
|
|
965
|
+
): LangGraphMessageSeen {
|
|
966
|
+
let seen = messageSeen.get(msgId);
|
|
967
|
+
if (!seen) {
|
|
968
|
+
seen = {};
|
|
969
|
+
messageSeen.set(msgId, seen);
|
|
970
|
+
}
|
|
971
|
+
return seen;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Returns the per-index tool call metadata for a message, creating it on first use.
|
|
976
|
+
* Later streamed chunks use this to recover tool call IDs/names from earlier chunks.
|
|
977
|
+
*/
|
|
978
|
+
function getOrCreateToolCallInfoByIndex(
|
|
979
|
+
toolCallInfoByIndex: LangGraphEventState['toolCallInfoByIndex'],
|
|
980
|
+
msgId: string,
|
|
981
|
+
): Map<number, { id: string; name: string }> {
|
|
982
|
+
let toolCallInfo = toolCallInfoByIndex.get(msgId);
|
|
983
|
+
if (!toolCallInfo) {
|
|
984
|
+
toolCallInfo = new Map();
|
|
985
|
+
toolCallInfoByIndex.set(msgId, toolCallInfo);
|
|
986
|
+
}
|
|
987
|
+
return toolCallInfo;
|
|
988
|
+
}
|
|
989
|
+
|
|
957
990
|
/**
|
|
958
991
|
* Processes a LangGraph event and emits UI message chunks.
|
|
959
992
|
*
|
|
@@ -1036,16 +1069,16 @@ export function processLangGraphEvent(
|
|
|
1036
1069
|
: null;
|
|
1037
1070
|
if (langgraphStep !== null && langgraphStep !== state.currentStep) {
|
|
1038
1071
|
if (state.currentStep !== null) {
|
|
1039
|
-
for (const [id, seen] of
|
|
1072
|
+
for (const [id, seen] of messageSeen) {
|
|
1040
1073
|
if (seen.text) {
|
|
1041
1074
|
controller.enqueue({ type: 'text-end', id });
|
|
1042
1075
|
}
|
|
1043
1076
|
if (seen.reasoning) {
|
|
1044
1077
|
controller.enqueue({ type: 'reasoning-end', id });
|
|
1045
1078
|
}
|
|
1046
|
-
delete
|
|
1047
|
-
delete
|
|
1048
|
-
delete
|
|
1079
|
+
messageSeen.delete(id);
|
|
1080
|
+
messageConcat.delete(id);
|
|
1081
|
+
messageReasoningIds.delete(id);
|
|
1049
1082
|
}
|
|
1050
1083
|
controller.enqueue({ type: 'finish-step' });
|
|
1051
1084
|
}
|
|
@@ -1058,17 +1091,19 @@ export function processLangGraphEvent(
|
|
|
1058
1091
|
* Note: Only works for actual class instances, not serialized messages
|
|
1059
1092
|
*/
|
|
1060
1093
|
if (AIMessageChunk.isInstance(msg)) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1094
|
+
const existingMessage = messageConcat.get(msgId);
|
|
1095
|
+
if (existingMessage) {
|
|
1096
|
+
messageConcat.set(
|
|
1097
|
+
msgId,
|
|
1098
|
+
existingMessage.concat(msg) as AIMessageChunk,
|
|
1099
|
+
);
|
|
1065
1100
|
} else {
|
|
1066
|
-
messageConcat
|
|
1101
|
+
messageConcat.set(msgId, msg);
|
|
1067
1102
|
}
|
|
1068
1103
|
}
|
|
1069
1104
|
|
|
1070
1105
|
if (isAIMessageChunk(msg)) {
|
|
1071
|
-
const concatChunk = messageConcat
|
|
1106
|
+
const concatChunk = messageConcat.get(msgId);
|
|
1072
1107
|
|
|
1073
1108
|
/**
|
|
1074
1109
|
* Handle image generation outputs from additional_kwargs.tool_outputs
|
|
@@ -1127,22 +1162,25 @@ export function processLangGraphEvent(
|
|
|
1127
1162
|
* If this chunk has an id, store it for future lookups by index
|
|
1128
1163
|
*/
|
|
1129
1164
|
if (toolCallChunk.id) {
|
|
1130
|
-
toolCallInfoByIndex
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1165
|
+
getOrCreateToolCallInfoByIndex(toolCallInfoByIndex, msgId).set(
|
|
1166
|
+
idx,
|
|
1167
|
+
{
|
|
1168
|
+
id: toolCallChunk.id,
|
|
1169
|
+
name:
|
|
1170
|
+
toolCallChunk.name ||
|
|
1171
|
+
concatChunk?.tool_call_chunks?.[idx]?.name ||
|
|
1172
|
+
'unknown',
|
|
1173
|
+
},
|
|
1174
|
+
);
|
|
1138
1175
|
}
|
|
1139
1176
|
|
|
1140
1177
|
/**
|
|
1141
1178
|
* Get the tool call ID from the chunk, stored info, or accumulated chunks
|
|
1142
1179
|
*/
|
|
1180
|
+
const storedToolCallInfo = toolCallInfoByIndex.get(msgId)?.get(idx);
|
|
1143
1181
|
const toolCallId =
|
|
1144
1182
|
toolCallChunk.id ||
|
|
1145
|
-
|
|
1183
|
+
storedToolCallInfo?.id ||
|
|
1146
1184
|
concatChunk?.tool_call_chunks?.[idx]?.id;
|
|
1147
1185
|
|
|
1148
1186
|
/**
|
|
@@ -1154,7 +1192,7 @@ export function processLangGraphEvent(
|
|
|
1154
1192
|
|
|
1155
1193
|
const toolName =
|
|
1156
1194
|
toolCallChunk.name ||
|
|
1157
|
-
|
|
1195
|
+
storedToolCallInfo?.name ||
|
|
1158
1196
|
concatChunk?.tool_call_chunks?.[idx]?.name ||
|
|
1159
1197
|
'unknown';
|
|
1160
1198
|
|
|
@@ -1163,18 +1201,21 @@ export function processLangGraphEvent(
|
|
|
1163
1201
|
* (even if args is empty - the first chunk often has empty args)
|
|
1164
1202
|
* Set dynamic: true to enable HITL approval requests
|
|
1165
1203
|
*/
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
dynamic: true,
|
|
1172
|
-
});
|
|
1204
|
+
const seen = messageSeen.get(msgId);
|
|
1205
|
+
if (!seen?.tool?.has(toolCallId)) {
|
|
1206
|
+
const updatedSeen = getOrCreateMessageSeen(messageSeen, msgId);
|
|
1207
|
+
updatedSeen.tool ??= new Set();
|
|
1208
|
+
updatedSeen.tool.add(toolCallId);
|
|
1173
1209
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1210
|
+
if (!emittedToolCalls.has(toolCallId)) {
|
|
1211
|
+
emittedToolCalls.add(toolCallId);
|
|
1212
|
+
controller.enqueue({
|
|
1213
|
+
type: 'tool-input-start',
|
|
1214
|
+
toolCallId: toolCallId,
|
|
1215
|
+
toolName: toolName,
|
|
1216
|
+
dynamic: true,
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1178
1219
|
}
|
|
1179
1220
|
|
|
1180
1221
|
/**
|
|
@@ -1205,8 +1246,8 @@ export function processLangGraphEvent(
|
|
|
1205
1246
|
// Capture reasoning ID when we first see it (even if no content yet)
|
|
1206
1247
|
const chunkReasoningId = extractReasoningId(msg);
|
|
1207
1248
|
if (chunkReasoningId) {
|
|
1208
|
-
if (!messageReasoningIds
|
|
1209
|
-
messageReasoningIds
|
|
1249
|
+
if (!messageReasoningIds.has(msgId)) {
|
|
1250
|
+
messageReasoningIds.set(msgId, chunkReasoningId);
|
|
1210
1251
|
}
|
|
1211
1252
|
// Immediately mark as emitted to prevent values from duplicating
|
|
1212
1253
|
// This must happen as soon as we see the ID, before content arrives
|
|
@@ -1217,12 +1258,12 @@ export function processLangGraphEvent(
|
|
|
1217
1258
|
if (reasoning) {
|
|
1218
1259
|
// Use stored reasoning ID, or current chunk's ID, or fall back to message ID
|
|
1219
1260
|
const reasoningId =
|
|
1220
|
-
messageReasoningIds
|
|
1261
|
+
messageReasoningIds.get(msgId) ?? chunkReasoningId ?? msgId;
|
|
1221
1262
|
|
|
1222
|
-
|
|
1263
|
+
const seen = messageSeen.get(msgId);
|
|
1264
|
+
if (!seen?.reasoning) {
|
|
1223
1265
|
controller.enqueue({ type: 'reasoning-start', id: msgId });
|
|
1224
|
-
messageSeen
|
|
1225
|
-
messageSeen[msgId].reasoning = true;
|
|
1266
|
+
getOrCreateMessageSeen(messageSeen, msgId).reasoning = true;
|
|
1226
1267
|
}
|
|
1227
1268
|
|
|
1228
1269
|
// Streaming chunks have delta text, emit directly without slicing
|
|
@@ -1240,10 +1281,10 @@ export function processLangGraphEvent(
|
|
|
1240
1281
|
*/
|
|
1241
1282
|
const text = getMessageText(msg);
|
|
1242
1283
|
if (text) {
|
|
1243
|
-
|
|
1284
|
+
const seen = messageSeen.get(msgId);
|
|
1285
|
+
if (!seen?.text) {
|
|
1244
1286
|
controller.enqueue({ type: 'text-start', id: msgId });
|
|
1245
|
-
messageSeen
|
|
1246
|
-
messageSeen[msgId].text = true;
|
|
1287
|
+
getOrCreateMessageSeen(messageSeen, msgId).text = true;
|
|
1247
1288
|
}
|
|
1248
1289
|
|
|
1249
1290
|
controller.enqueue({
|
|
@@ -1294,16 +1335,16 @@ export function processLangGraphEvent(
|
|
|
1294
1335
|
/**
|
|
1295
1336
|
* Finalize all pending message chunks
|
|
1296
1337
|
*/
|
|
1297
|
-
for (const [id, seen] of
|
|
1338
|
+
for (const [id, seen] of messageSeen) {
|
|
1298
1339
|
if (seen.text) controller.enqueue({ type: 'text-end', id });
|
|
1299
1340
|
if (seen.tool) {
|
|
1300
|
-
for (const
|
|
1301
|
-
const concatMsg = messageConcat
|
|
1341
|
+
for (const toolCallId of seen.tool) {
|
|
1342
|
+
const concatMsg = messageConcat.get(id);
|
|
1302
1343
|
const toolCall = concatMsg?.tool_calls?.find(
|
|
1303
1344
|
call => call.id === toolCallId,
|
|
1304
1345
|
);
|
|
1305
1346
|
|
|
1306
|
-
if (
|
|
1347
|
+
if (toolCall) {
|
|
1307
1348
|
emittedToolCalls.add(toolCallId);
|
|
1308
1349
|
// Store mapping for HITL interrupt lookup
|
|
1309
1350
|
const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;
|
|
@@ -1323,9 +1364,9 @@ export function processLangGraphEvent(
|
|
|
1323
1364
|
controller.enqueue({ type: 'reasoning-end', id });
|
|
1324
1365
|
}
|
|
1325
1366
|
|
|
1326
|
-
delete
|
|
1327
|
-
delete
|
|
1328
|
-
delete
|
|
1367
|
+
messageSeen.delete(id);
|
|
1368
|
+
messageConcat.delete(id);
|
|
1369
|
+
messageReasoningIds.delete(id);
|
|
1329
1370
|
}
|
|
1330
1371
|
|
|
1331
1372
|
/**
|
|
@@ -1498,7 +1539,7 @@ export function processLangGraphEvent(
|
|
|
1498
1539
|
* AND tool_calls. We skip those to avoid duplicate reasoning entries.)
|
|
1499
1540
|
*/
|
|
1500
1541
|
const reasoningId = extractReasoningId(msg);
|
|
1501
|
-
const wasStreamedThisRequest =
|
|
1542
|
+
const wasStreamedThisRequest = messageSeen.has(msgId);
|
|
1502
1543
|
const hasToolCalls = toolCalls && toolCalls.length > 0;
|
|
1503
1544
|
|
|
1504
1545
|
/**
|