@langchain/langgraph-api 1.1.17 → 1.2.1
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/api/protocol.d.mts +7 -0
- package/dist/api/protocol.mjs +157 -0
- package/dist/api/runs.mjs +15 -4
- package/dist/command.mjs +1 -1
- package/dist/experimental/embed/constants.d.mts +3 -0
- package/dist/experimental/embed/constants.mjs +11 -0
- package/dist/experimental/embed/protocol.d.mts +9 -0
- package/dist/experimental/embed/protocol.mjs +453 -0
- package/dist/experimental/embed/runs.d.mts +8 -0
- package/dist/experimental/embed/runs.mjs +202 -0
- package/dist/experimental/embed/threads.d.mts +8 -0
- package/dist/experimental/embed/threads.mjs +151 -0
- package/dist/experimental/embed/types.d.mts +77 -0
- package/dist/experimental/embed/types.mjs +1 -0
- package/dist/experimental/embed/utils.d.mts +29 -0
- package/dist/experimental/embed/utils.mjs +62 -0
- package/dist/experimental/embed.d.mts +11 -31
- package/dist/experimental/embed.mjs +19 -399
- package/dist/graph/load.d.mts +2 -2
- package/dist/graph/load.utils.mjs +13 -3
- package/dist/protocol/constants.d.mts +7 -0
- package/dist/protocol/constants.mjs +7 -0
- package/dist/protocol/service.d.mts +101 -0
- package/dist/protocol/service.mjs +568 -0
- package/dist/protocol/session/event-normalizers.d.mts +52 -0
- package/dist/protocol/session/event-normalizers.mjs +162 -0
- package/dist/protocol/session/index.d.mts +261 -0
- package/dist/protocol/session/index.mjs +826 -0
- package/dist/protocol/session/internal-types.d.mts +67 -0
- package/dist/protocol/session/internal-types.mjs +28 -0
- package/dist/protocol/session/metadata.d.mts +24 -0
- package/dist/protocol/session/metadata.mjs +95 -0
- package/dist/protocol/session/namespace.d.mts +47 -0
- package/dist/protocol/session/namespace.mjs +62 -0
- package/dist/protocol/session/state-normalizers.d.mts +57 -0
- package/dist/protocol/session/state-normalizers.mjs +430 -0
- package/dist/protocol/session/tool-calls.d.mts +27 -0
- package/dist/protocol/session/tool-calls.mjs +59 -0
- package/dist/protocol/types.d.mts +121 -0
- package/dist/protocol/types.mjs +1 -0
- package/dist/queue.mjs +8 -2
- package/dist/schemas.d.mts +58 -58
- package/dist/semver/index.mjs +19 -1
- package/dist/server.mjs +22 -3
- package/dist/state.mjs +1 -1
- package/dist/storage/ops.mjs +17 -5
- package/dist/storage/persist.mjs +1 -1
- package/dist/storage/types.d.mts +10 -1
- package/dist/stream.d.mts +25 -4
- package/dist/stream.mjs +203 -11
- package/dist/utils/serde.mjs +1 -1
- package/package.json +16 -14
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { isRecord } from "./internal-types.mjs";
|
|
2
|
+
import { getTupleToolCallArgs, getTupleToolCallIdentity, normalizeFinalToolCallArgs, } from "./tool-calls.mjs";
|
|
3
|
+
const PROTOCOL_STATE_MESSAGE_TYPES = new Set([
|
|
4
|
+
"human",
|
|
5
|
+
"user",
|
|
6
|
+
"ai",
|
|
7
|
+
"assistant",
|
|
8
|
+
"system",
|
|
9
|
+
"tool",
|
|
10
|
+
"function",
|
|
11
|
+
"remove",
|
|
12
|
+
]);
|
|
13
|
+
const MIME_TYPE_BY_AUDIO_FORMAT = {
|
|
14
|
+
mp3: "audio/mpeg",
|
|
15
|
+
wav: "audio/wav",
|
|
16
|
+
pcm16: "audio/wav",
|
|
17
|
+
pcm: "audio/wav",
|
|
18
|
+
opus: "audio/opus",
|
|
19
|
+
aac: "audio/aac",
|
|
20
|
+
flac: "audio/flac",
|
|
21
|
+
};
|
|
22
|
+
const PROTOCOL_CONTENT_BLOCK_TYPES = new Set([
|
|
23
|
+
"text",
|
|
24
|
+
"reasoning",
|
|
25
|
+
"tool_call",
|
|
26
|
+
"tool_call_chunk",
|
|
27
|
+
"invalid_tool_call",
|
|
28
|
+
"server_tool_call",
|
|
29
|
+
"server_tool_call_chunk",
|
|
30
|
+
"server_tool_call_result",
|
|
31
|
+
"image",
|
|
32
|
+
"audio",
|
|
33
|
+
"video",
|
|
34
|
+
"file",
|
|
35
|
+
"non_standard",
|
|
36
|
+
]);
|
|
37
|
+
const normalizeAudioBlockFromAdditionalKwargs = (additionalKwargs) => {
|
|
38
|
+
const audio = isRecord(additionalKwargs?.audio)
|
|
39
|
+
? additionalKwargs.audio
|
|
40
|
+
: undefined;
|
|
41
|
+
if (audio == null)
|
|
42
|
+
return undefined;
|
|
43
|
+
const data = typeof audio.data === "string" ? audio.data : undefined;
|
|
44
|
+
const url = typeof audio.url === "string" ? audio.url : undefined;
|
|
45
|
+
if (data == null && url == null)
|
|
46
|
+
return undefined;
|
|
47
|
+
const explicitMimeType = typeof audio.mime_type === "string"
|
|
48
|
+
? audio.mime_type
|
|
49
|
+
: typeof audio.mimeType === "string"
|
|
50
|
+
? audio.mimeType
|
|
51
|
+
: undefined;
|
|
52
|
+
const format = typeof audio.format === "string"
|
|
53
|
+
? audio.format.toLowerCase()
|
|
54
|
+
: explicitMimeType != null
|
|
55
|
+
? undefined
|
|
56
|
+
: "wav";
|
|
57
|
+
return {
|
|
58
|
+
type: "audio",
|
|
59
|
+
...(typeof audio.id === "string" ? { id: audio.id } : {}),
|
|
60
|
+
...(url != null ? { url } : {}),
|
|
61
|
+
...(data != null ? { data } : {}),
|
|
62
|
+
...(explicitMimeType != null
|
|
63
|
+
? { mime_type: explicitMimeType }
|
|
64
|
+
: format != null && MIME_TYPE_BY_AUDIO_FORMAT[format] != null
|
|
65
|
+
? { mime_type: MIME_TYPE_BY_AUDIO_FORMAT[format] }
|
|
66
|
+
: {}),
|
|
67
|
+
...(typeof audio.transcript === "string"
|
|
68
|
+
? { transcript: audio.transcript }
|
|
69
|
+
: {}),
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const MEDIA_BLOCK_TYPES = new Set([
|
|
73
|
+
"image",
|
|
74
|
+
"audio",
|
|
75
|
+
"video",
|
|
76
|
+
"file",
|
|
77
|
+
]);
|
|
78
|
+
const MIME_TYPE_BY_IMAGE_FORMAT = {
|
|
79
|
+
png: "image/png",
|
|
80
|
+
jpeg: "image/jpeg",
|
|
81
|
+
jpg: "image/jpeg",
|
|
82
|
+
webp: "image/webp",
|
|
83
|
+
gif: "image/gif",
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Extracts OpenAI Responses API `image_generation_call` outputs from
|
|
87
|
+
* `additional_kwargs.tool_outputs` as protocol-shaped image blocks. These
|
|
88
|
+
* outputs carry base64 image payloads that do not appear in
|
|
89
|
+
* `message.content`, so we lift them into standard blocks for downstream
|
|
90
|
+
* consumers (including synthetic subagent emission on the public namespace).
|
|
91
|
+
*
|
|
92
|
+
* @param additionalKwargs - The message's `additional_kwargs` record.
|
|
93
|
+
* @returns An array of image blocks in the order they appear, or `[]`.
|
|
94
|
+
*/
|
|
95
|
+
const normalizeImageBlocksFromAdditionalKwargs = (additionalKwargs) => {
|
|
96
|
+
const toolOutputs = additionalKwargs?.tool_outputs;
|
|
97
|
+
if (!Array.isArray(toolOutputs))
|
|
98
|
+
return [];
|
|
99
|
+
const blocks = [];
|
|
100
|
+
for (const entry of toolOutputs) {
|
|
101
|
+
if (!isRecord(entry) || entry.type !== "image_generation_call")
|
|
102
|
+
continue;
|
|
103
|
+
const data = typeof entry.result === "string" ? entry.result : undefined;
|
|
104
|
+
const url = typeof entry.url === "string" ? entry.url : undefined;
|
|
105
|
+
if (data == null && url == null)
|
|
106
|
+
continue;
|
|
107
|
+
const outputFormat = typeof entry.output_format === "string"
|
|
108
|
+
? entry.output_format.toLowerCase()
|
|
109
|
+
: undefined;
|
|
110
|
+
const mimeType = (outputFormat != null
|
|
111
|
+
? MIME_TYPE_BY_IMAGE_FORMAT[outputFormat]
|
|
112
|
+
: undefined) ?? "image/png";
|
|
113
|
+
blocks.push({
|
|
114
|
+
type: "image",
|
|
115
|
+
...(typeof entry.id === "string" ? { id: entry.id } : {}),
|
|
116
|
+
...(url != null ? { url } : {}),
|
|
117
|
+
...(data != null ? { data } : {}),
|
|
118
|
+
mime_type: mimeType,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return blocks;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Converts common camelCase field names produced by LangChain content blocks
|
|
125
|
+
* (e.g. `mimeType`) into the snake_case shape mandated by the protocol
|
|
126
|
+
* (`mime_type`). This keeps media blocks emitted by providers such as
|
|
127
|
+
* `ChatOpenAI` (Responses API `image_generation` tool) readable by downstream
|
|
128
|
+
* clients without requiring each provider to opt into protocol casing.
|
|
129
|
+
*/
|
|
130
|
+
const normalizeMediaBlockCasing = (block) => {
|
|
131
|
+
if (!MEDIA_BLOCK_TYPES.has(block.type))
|
|
132
|
+
return block;
|
|
133
|
+
const { mimeType, ...rest } = block;
|
|
134
|
+
if (typeof mimeType !== "string")
|
|
135
|
+
return block;
|
|
136
|
+
if (typeof rest.mime_type === "string")
|
|
137
|
+
return rest;
|
|
138
|
+
return { ...rest, mime_type: mimeType };
|
|
139
|
+
};
|
|
140
|
+
export const normalizeProtocolContentBlock = (value) => {
|
|
141
|
+
if (!isRecord(value) || typeof value.type !== "string")
|
|
142
|
+
return undefined;
|
|
143
|
+
if (PROTOCOL_CONTENT_BLOCK_TYPES.has(value.type)) {
|
|
144
|
+
return normalizeMediaBlockCasing(value);
|
|
145
|
+
}
|
|
146
|
+
if (value.type === "image_url") {
|
|
147
|
+
const rawImage = value.image_url;
|
|
148
|
+
if (typeof rawImage === "string") {
|
|
149
|
+
return { type: "image", url: rawImage };
|
|
150
|
+
}
|
|
151
|
+
if (isRecord(rawImage) && typeof rawImage.url === "string") {
|
|
152
|
+
return {
|
|
153
|
+
type: "image",
|
|
154
|
+
url: rawImage.url,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
if (value.type === "input_audio") {
|
|
160
|
+
const rawAudio = isRecord(value.input_audio)
|
|
161
|
+
? value.input_audio
|
|
162
|
+
: undefined;
|
|
163
|
+
if (rawAudio == null)
|
|
164
|
+
return undefined;
|
|
165
|
+
return {
|
|
166
|
+
type: "audio",
|
|
167
|
+
...(typeof rawAudio.data === "string" ? { data: rawAudio.data } : {}),
|
|
168
|
+
...(typeof rawAudio.mimeType === "string"
|
|
169
|
+
? { mimeType: rawAudio.mimeType }
|
|
170
|
+
: {}),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
type: "non_standard",
|
|
175
|
+
value: { ...value },
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
export const normalizeProtocolMessageContent = (content, options) => {
|
|
179
|
+
const additionalKwargs = options?.additionalKwargs;
|
|
180
|
+
const extractExtras = () => {
|
|
181
|
+
const audioBlock = normalizeAudioBlockFromAdditionalKwargs(additionalKwargs);
|
|
182
|
+
const imageBlocks = normalizeImageBlocksFromAdditionalKwargs(additionalKwargs);
|
|
183
|
+
return { audioBlock, imageBlocks };
|
|
184
|
+
};
|
|
185
|
+
if (typeof content === "string") {
|
|
186
|
+
const { audioBlock, imageBlocks } = extractExtras();
|
|
187
|
+
if (audioBlock == null && imageBlocks.length === 0)
|
|
188
|
+
return content;
|
|
189
|
+
const blocks = [];
|
|
190
|
+
if (content.length > 0) {
|
|
191
|
+
blocks.push({ type: "text", text: content });
|
|
192
|
+
}
|
|
193
|
+
blocks.push(...imageBlocks);
|
|
194
|
+
if (audioBlock != null)
|
|
195
|
+
blocks.push(audioBlock);
|
|
196
|
+
return blocks;
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(content)) {
|
|
199
|
+
const { audioBlock, imageBlocks } = extractExtras();
|
|
200
|
+
if (audioBlock == null && imageBlocks.length === 0)
|
|
201
|
+
return content;
|
|
202
|
+
const blocks = [...imageBlocks];
|
|
203
|
+
if (audioBlock != null)
|
|
204
|
+
blocks.push(audioBlock);
|
|
205
|
+
return blocks;
|
|
206
|
+
}
|
|
207
|
+
const blocks = [];
|
|
208
|
+
for (const entry of content) {
|
|
209
|
+
if (typeof entry === "string") {
|
|
210
|
+
blocks.push({ type: "text", text: entry });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const normalized = normalizeProtocolContentBlock(entry);
|
|
214
|
+
if (normalized != null) {
|
|
215
|
+
blocks.push(normalized);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const { audioBlock, imageBlocks } = extractExtras();
|
|
219
|
+
for (const imageBlock of imageBlocks) {
|
|
220
|
+
const imageId = imageBlock.id;
|
|
221
|
+
const hasMatchingImage = blocks.some((block) => block.type === "image" &&
|
|
222
|
+
block.id === imageId &&
|
|
223
|
+
imageId != null);
|
|
224
|
+
if (!hasMatchingImage)
|
|
225
|
+
blocks.push(imageBlock);
|
|
226
|
+
}
|
|
227
|
+
if (audioBlock != null && !blocks.some((block) => block.type === "audio")) {
|
|
228
|
+
blocks.push(audioBlock);
|
|
229
|
+
}
|
|
230
|
+
return blocks.length > 0 ? blocks : content;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Normalizes protocol message type aliases into LangChain core message types.
|
|
234
|
+
*
|
|
235
|
+
* @param value - Raw message type.
|
|
236
|
+
* @returns The normalized message type, if recognized.
|
|
237
|
+
*/
|
|
238
|
+
export const normalizeProtocolStateMessageType = (value) => {
|
|
239
|
+
switch (value) {
|
|
240
|
+
case "assistant":
|
|
241
|
+
return "ai";
|
|
242
|
+
case "user":
|
|
243
|
+
return "human";
|
|
244
|
+
default:
|
|
245
|
+
return typeof value === "string" ? value : undefined;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Checks whether a value matches the message shape used in state payloads.
|
|
250
|
+
*
|
|
251
|
+
* @param value - Raw state value.
|
|
252
|
+
* @returns Whether the value is a protocol-state message object.
|
|
253
|
+
*/
|
|
254
|
+
export const isProtocolStateMessage = (value) => isRecord(value) &&
|
|
255
|
+
PROTOCOL_STATE_MESSAGE_TYPES.has(normalizeProtocolStateMessageType(value.type) ?? "");
|
|
256
|
+
/**
|
|
257
|
+
* Normalizes invalid tool calls embedded in serialized AI messages.
|
|
258
|
+
*
|
|
259
|
+
* @param value - Raw invalid tool call list.
|
|
260
|
+
* @returns Normalized invalid tool calls.
|
|
261
|
+
*/
|
|
262
|
+
export const normalizeProtocolStateInvalidToolCalls = (value) => {
|
|
263
|
+
if (!Array.isArray(value))
|
|
264
|
+
return [];
|
|
265
|
+
const invalidToolCalls = [];
|
|
266
|
+
for (const rawInvalidToolCall of value) {
|
|
267
|
+
if (!isRecord(rawInvalidToolCall))
|
|
268
|
+
continue;
|
|
269
|
+
const identity = getTupleToolCallIdentity(rawInvalidToolCall);
|
|
270
|
+
invalidToolCalls.push({
|
|
271
|
+
...(identity.id != null ? { id: identity.id } : {}),
|
|
272
|
+
...(identity.name != null ? { name: identity.name } : {}),
|
|
273
|
+
...(typeof rawInvalidToolCall.args === "string"
|
|
274
|
+
? { args: rawInvalidToolCall.args }
|
|
275
|
+
: {}),
|
|
276
|
+
error: typeof rawInvalidToolCall.error === "string"
|
|
277
|
+
? rawInvalidToolCall.error
|
|
278
|
+
: "Malformed args.",
|
|
279
|
+
type: "invalid_tool_call",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return invalidToolCalls;
|
|
283
|
+
};
|
|
284
|
+
/**
|
|
285
|
+
* Normalizes tool calls embedded in serialized AI messages.
|
|
286
|
+
*
|
|
287
|
+
* @param value - Raw tool call list.
|
|
288
|
+
* @returns Normalized valid and invalid tool call arrays.
|
|
289
|
+
*/
|
|
290
|
+
export const normalizeProtocolStateToolCalls = (value) => {
|
|
291
|
+
if (!Array.isArray(value)) {
|
|
292
|
+
return {
|
|
293
|
+
toolCalls: [],
|
|
294
|
+
invalidToolCalls: [],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const toolCalls = [];
|
|
298
|
+
const invalidToolCalls = [];
|
|
299
|
+
for (const rawToolCall of value) {
|
|
300
|
+
if (!isRecord(rawToolCall))
|
|
301
|
+
continue;
|
|
302
|
+
const identity = getTupleToolCallIdentity(rawToolCall);
|
|
303
|
+
const rawArgs = getTupleToolCallArgs(rawToolCall);
|
|
304
|
+
const normalizedArgs = normalizeFinalToolCallArgs(rawArgs);
|
|
305
|
+
if (identity.name == null) {
|
|
306
|
+
invalidToolCalls.push({
|
|
307
|
+
...(identity.id != null ? { id: identity.id } : {}),
|
|
308
|
+
...(typeof rawArgs === "string" ? { args: rawArgs } : {}),
|
|
309
|
+
error: "Incomplete tool call.",
|
|
310
|
+
type: "invalid_tool_call",
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (!normalizedArgs.valid) {
|
|
315
|
+
invalidToolCalls.push({
|
|
316
|
+
...(identity.id != null ? { id: identity.id } : {}),
|
|
317
|
+
name: identity.name,
|
|
318
|
+
...(typeof normalizedArgs.args === "string"
|
|
319
|
+
? { args: normalizedArgs.args }
|
|
320
|
+
: {}),
|
|
321
|
+
error: "Malformed args.",
|
|
322
|
+
type: "invalid_tool_call",
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
toolCalls.push({
|
|
327
|
+
...(identity.id != null ? { id: identity.id } : {}),
|
|
328
|
+
name: identity.name,
|
|
329
|
+
args: normalizedArgs.args,
|
|
330
|
+
type: "tool_call",
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return { toolCalls, invalidToolCalls };
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Normalizes a single message embedded in protocol state payloads.
|
|
337
|
+
*
|
|
338
|
+
* @param value - Raw message object.
|
|
339
|
+
* @returns The normalized message shape.
|
|
340
|
+
*/
|
|
341
|
+
export const normalizeProtocolStateMessage = (value) => {
|
|
342
|
+
const type = normalizeProtocolStateMessageType(value.type);
|
|
343
|
+
if (type == null)
|
|
344
|
+
return value;
|
|
345
|
+
const additionalKwargs = isRecord(value.additional_kwargs)
|
|
346
|
+
? value.additional_kwargs
|
|
347
|
+
: undefined;
|
|
348
|
+
const message = {
|
|
349
|
+
type,
|
|
350
|
+
content: normalizeProtocolMessageContent("content" in value ? value.content : "", {
|
|
351
|
+
additionalKwargs: type === "ai" ? additionalKwargs : undefined,
|
|
352
|
+
}),
|
|
353
|
+
};
|
|
354
|
+
if (typeof value.id === "string") {
|
|
355
|
+
message.id = value.id;
|
|
356
|
+
}
|
|
357
|
+
if (typeof value.name === "string") {
|
|
358
|
+
message.name = value.name;
|
|
359
|
+
}
|
|
360
|
+
if ((type === "ai" || type === "human") &&
|
|
361
|
+
typeof value.example === "boolean") {
|
|
362
|
+
message.example = value.example;
|
|
363
|
+
}
|
|
364
|
+
if (type === "tool") {
|
|
365
|
+
if (typeof value.tool_call_id === "string") {
|
|
366
|
+
message.tool_call_id = value.tool_call_id;
|
|
367
|
+
}
|
|
368
|
+
if (value.status === "success" || value.status === "error") {
|
|
369
|
+
message.status = value.status;
|
|
370
|
+
}
|
|
371
|
+
if ("artifact" in value) {
|
|
372
|
+
message.artifact = value.artifact;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (type === "ai") {
|
|
376
|
+
const rawToolCalls = Array.isArray(value.tool_calls) && value.tool_calls.length > 0
|
|
377
|
+
? value.tool_calls
|
|
378
|
+
: Array.isArray(additionalKwargs?.tool_calls)
|
|
379
|
+
? additionalKwargs.tool_calls
|
|
380
|
+
: undefined;
|
|
381
|
+
const normalizedToolCalls = normalizeProtocolStateToolCalls(rawToolCalls);
|
|
382
|
+
const normalizedInvalidToolCalls = Array.isArray(value.invalid_tool_calls) &&
|
|
383
|
+
value.invalid_tool_calls.length > 0
|
|
384
|
+
? normalizeProtocolStateInvalidToolCalls(value.invalid_tool_calls)
|
|
385
|
+
: normalizedToolCalls.invalidToolCalls;
|
|
386
|
+
if (normalizedToolCalls.toolCalls.length > 0) {
|
|
387
|
+
message.tool_calls = normalizedToolCalls.toolCalls;
|
|
388
|
+
}
|
|
389
|
+
if (normalizedInvalidToolCalls.length > 0) {
|
|
390
|
+
message.invalid_tool_calls = normalizedInvalidToolCalls;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return message;
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
* Recursively normalizes protocol state payloads before they are emitted.
|
|
397
|
+
*
|
|
398
|
+
* @param value - Raw state payload.
|
|
399
|
+
* @returns The normalized state payload.
|
|
400
|
+
*/
|
|
401
|
+
export const normalizeProtocolStatePayload = (value) => {
|
|
402
|
+
if (Array.isArray(value)) {
|
|
403
|
+
return value.map((item) => isProtocolStateMessage(item)
|
|
404
|
+
? normalizeProtocolStateMessage(item)
|
|
405
|
+
: normalizeProtocolStatePayload(item));
|
|
406
|
+
}
|
|
407
|
+
if (!isRecord(value))
|
|
408
|
+
return value;
|
|
409
|
+
const normalized = {};
|
|
410
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
411
|
+
if (key === "__interrupt__") {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (key === "messages" && Array.isArray(entry)) {
|
|
415
|
+
normalized[key] = entry.map((item) => isProtocolStateMessage(item)
|
|
416
|
+
? normalizeProtocolStateMessage(item)
|
|
417
|
+
: item);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
normalized[key] = normalizeProtocolStatePayload(entry);
|
|
421
|
+
}
|
|
422
|
+
return normalized;
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Coerces arbitrary update payloads into the protocol values shape.
|
|
426
|
+
*
|
|
427
|
+
* @param value - Raw update payload.
|
|
428
|
+
* @returns A valid updates values payload.
|
|
429
|
+
*/
|
|
430
|
+
export const asUpdateValues = (value) => isRecord(value) ? value : { value };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the stable identity fields for a tuple tool call.
|
|
3
|
+
*
|
|
4
|
+
* @param value - Serialized tool call payload.
|
|
5
|
+
* @returns The resolved identifier and function name, when present.
|
|
6
|
+
*/
|
|
7
|
+
export declare const getTupleToolCallIdentity: (value: Record<string, unknown>) => {
|
|
8
|
+
id: string | undefined;
|
|
9
|
+
name: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Extracts the raw argument payload for a tuple tool call.
|
|
13
|
+
*
|
|
14
|
+
* @param value - Serialized tool call payload.
|
|
15
|
+
* @returns The raw arguments field, if present.
|
|
16
|
+
*/
|
|
17
|
+
export declare const getTupleToolCallArgs: (value: Record<string, unknown>) => unknown;
|
|
18
|
+
/**
|
|
19
|
+
* Parses the final argument payload for a completed tool call.
|
|
20
|
+
*
|
|
21
|
+
* @param value - Raw tool call arguments.
|
|
22
|
+
* @returns The parsed arguments plus a validity flag.
|
|
23
|
+
*/
|
|
24
|
+
export declare const normalizeFinalToolCallArgs: (value: unknown) => {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
args: unknown;
|
|
27
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isRecord } from "./internal-types.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the stable identity fields for a tuple tool call.
|
|
4
|
+
*
|
|
5
|
+
* @param value - Serialized tool call payload.
|
|
6
|
+
* @returns The resolved identifier and function name, when present.
|
|
7
|
+
*/
|
|
8
|
+
export const getTupleToolCallIdentity = (value) => {
|
|
9
|
+
const nestedFunction = isRecord(value.function) ? value.function : undefined;
|
|
10
|
+
return {
|
|
11
|
+
id: typeof value.id === "string" ? value.id : undefined,
|
|
12
|
+
name: typeof value.name === "string"
|
|
13
|
+
? value.name
|
|
14
|
+
: typeof nestedFunction?.name === "string"
|
|
15
|
+
? nestedFunction.name
|
|
16
|
+
: undefined,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Extracts the raw argument payload for a tuple tool call.
|
|
21
|
+
*
|
|
22
|
+
* @param value - Serialized tool call payload.
|
|
23
|
+
* @returns The raw arguments field, if present.
|
|
24
|
+
*/
|
|
25
|
+
export const getTupleToolCallArgs = (value) => {
|
|
26
|
+
if ("args" in value)
|
|
27
|
+
return value.args;
|
|
28
|
+
const nestedFunction = isRecord(value.function) ? value.function : undefined;
|
|
29
|
+
return nestedFunction?.arguments;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Parses the final argument payload for a completed tool call.
|
|
33
|
+
*
|
|
34
|
+
* @param value - Raw tool call arguments.
|
|
35
|
+
* @returns The parsed arguments plus a validity flag.
|
|
36
|
+
*/
|
|
37
|
+
export const normalizeFinalToolCallArgs = (value) => {
|
|
38
|
+
if (isRecord(value)) {
|
|
39
|
+
return { valid: true, args: value };
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
if (value.length === 0) {
|
|
43
|
+
return { valid: true, args: {} };
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return {
|
|
47
|
+
valid: true,
|
|
48
|
+
args: JSON.parse(value),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { valid: false, args: value };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (value == null) {
|
|
56
|
+
return { valid: true, args: {} };
|
|
57
|
+
}
|
|
58
|
+
return { valid: true, args: value };
|
|
59
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AgentResult, AgentStatus, AgentTreeNode, Channel, Checkpoint, CheckpointSource, Command, CommandResponse, ContentBlockDeltaData, ContentBlockFinishData, ContentBlockStartData, CustomData, ErrorCode, ErrorResponse, Event, LifecycleCause, MessageErrorData, MessageFinishData, MessageMetadata, MessageStartData, MessagesData, Namespace, ResponseMeta, RunStartParams, RunResult, StateGetResult, SubscribeParams, SubscribeResult, ToolErrorData, ToolFinishedData, ToolOutputDeltaData, ToolStartedData, ToolsData, UnsubscribeParams, UpdatesEvent } from "@langchain/protocol";
|
|
2
|
+
export type { LifecycleCause };
|
|
3
|
+
import type { AuthContext } from "../auth/index.mjs";
|
|
4
|
+
import type { RunProtocolSession } from "./session/index.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Raw events emitted by the existing LangGraph run stream implementation
|
|
7
|
+
* before they are normalized into protocol-framed events.
|
|
8
|
+
*
|
|
9
|
+
* The session emits two independent events per persisted checkpoint:
|
|
10
|
+
* - `event: "values"` carries the full state snapshot on the `values`
|
|
11
|
+
* protocol channel.
|
|
12
|
+
* - `event: "checkpoints"` carries the lightweight {@link Checkpoint}
|
|
13
|
+
* envelope on the dedicated `checkpoints` channel, paired with the
|
|
14
|
+
* adjacent `values` event by `(namespace, step)` so fork/time-travel
|
|
15
|
+
* UIs can subscribe without also paying for full-state payloads.
|
|
16
|
+
*
|
|
17
|
+
* When {@link normalized} is `true` the payload has already been converted
|
|
18
|
+
* to its protocol shape by the in-process streaming layer
|
|
19
|
+
* (`streamEvents(..., { version: "v3" })`) and should be passed through
|
|
20
|
+
* without re-normalization.
|
|
21
|
+
*/
|
|
22
|
+
export type SourceStreamEvent = {
|
|
23
|
+
id?: string;
|
|
24
|
+
event: string;
|
|
25
|
+
data: unknown;
|
|
26
|
+
normalized?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Transport profiles currently exposed by the LangGraph API implementation.
|
|
30
|
+
*/
|
|
31
|
+
export type ProtocolTransportName = "websocket" | "sse-http";
|
|
32
|
+
export type SupportedChannel = Extract<Channel, "values" | "updates" | "checkpoints" | "messages" | "tools" | "custom" | "lifecycle" | "input" | "tasks">;
|
|
33
|
+
export type EventMethodByChannel = {
|
|
34
|
+
values: "values";
|
|
35
|
+
updates: "updates";
|
|
36
|
+
checkpoints: "checkpoints";
|
|
37
|
+
messages: "messages";
|
|
38
|
+
tools: "tools";
|
|
39
|
+
custom: "custom";
|
|
40
|
+
lifecycle: "lifecycle";
|
|
41
|
+
input: "input.requested";
|
|
42
|
+
tasks: "tasks";
|
|
43
|
+
};
|
|
44
|
+
export type ProtocolResponseMeta = ResponseMeta;
|
|
45
|
+
export type ProtocolSuccess = CommandResponse;
|
|
46
|
+
export type ProtocolError = ErrorResponse;
|
|
47
|
+
export type ProtocolEvent = Event;
|
|
48
|
+
export type ProtocolCommand = Command;
|
|
49
|
+
export type ProtocolCommandByMethod<Method extends ProtocolCommand["method"]> = Extract<ProtocolCommand, {
|
|
50
|
+
method: Method;
|
|
51
|
+
}>;
|
|
52
|
+
export type ProtocolEventByMethod<Method extends SupportedChannel> = Extract<ProtocolEvent, {
|
|
53
|
+
method: EventMethodByChannel[Method];
|
|
54
|
+
}>;
|
|
55
|
+
export type ProtocolEventDataByMethod<Method extends SupportedChannel> = ProtocolEventByMethod<Method>["params"]["data"];
|
|
56
|
+
export type { AgentResult, AgentStatus, AgentTreeNode, Checkpoint, CheckpointSource, ContentBlockDeltaData, ContentBlockFinishData, ContentBlockStartData, CustomData, ErrorCode, MessageErrorData, MessageFinishData, MessageMetadata, MessagesData, MessageStartData, Namespace, RunStartParams, RunResult, StateGetResult, SubscribeParams, SubscribeResult, ToolErrorData, ToolFinishedData, ToolOutputDeltaData, ToolStartedData, ToolsData, UnsubscribeParams, UpdatesEvent, };
|
|
57
|
+
/**
|
|
58
|
+
* Per-connection filter for SSE event sinks.
|
|
59
|
+
*
|
|
60
|
+
* Each SSE `POST .../events` connection carries its own filter so the server
|
|
61
|
+
* can deliver only matching events without persisting subscription state.
|
|
62
|
+
*/
|
|
63
|
+
export type EventSinkFilter = {
|
|
64
|
+
channels: Set<string>;
|
|
65
|
+
namespaces?: string[][];
|
|
66
|
+
depth?: number;
|
|
67
|
+
since?: number;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* A single SSE event sink attached to a thread.
|
|
71
|
+
*
|
|
72
|
+
* `pendingReplay` is true while {@link ProtocolService.attachFilteredEventSink}
|
|
73
|
+
* is draining buffered events into this sink. While true, the live `send`
|
|
74
|
+
* callback must skip this sink so that the replay loop — not the live path —
|
|
75
|
+
* owns strict in-order delivery.
|
|
76
|
+
*/
|
|
77
|
+
export type EventSinkEntry = {
|
|
78
|
+
id: string;
|
|
79
|
+
filter: EventSinkFilter;
|
|
80
|
+
send: (message: ProtocolEvent) => Promise<void> | void;
|
|
81
|
+
pendingReplay?: boolean;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Runtime state tracked for an active thread connection.
|
|
85
|
+
*
|
|
86
|
+
* In the thread-centric protocol, threads are durable but ephemeral
|
|
87
|
+
* connection state lives here: the active run session, attached SSE
|
|
88
|
+
* sinks, and a queue of events waiting for a sink.
|
|
89
|
+
*/
|
|
90
|
+
export type ThreadRecord = {
|
|
91
|
+
threadId: string;
|
|
92
|
+
transport: ProtocolTransportName;
|
|
93
|
+
auth?: AuthContext;
|
|
94
|
+
assistantId?: string;
|
|
95
|
+
seq: number;
|
|
96
|
+
session?: RunProtocolSession;
|
|
97
|
+
currentRunId?: string;
|
|
98
|
+
/** WebSocket-only: single event delivery callback. */
|
|
99
|
+
sendEvent?: ((message: ProtocolEvent) => Promise<void> | void) | undefined;
|
|
100
|
+
/** SSE: per-connection filtered event sinks. */
|
|
101
|
+
eventSinks: Map<string, EventSinkEntry>;
|
|
102
|
+
/** Events buffered when no sink is attached yet. */
|
|
103
|
+
queuedEvents: ProtocolEvent[];
|
|
104
|
+
/** WebSocket-only: subscription commands replayed on new run sessions. */
|
|
105
|
+
activeSubscriptions: ProtocolCommand[];
|
|
106
|
+
/**
|
|
107
|
+
* WebSocket-only: subscribes that arrived before a run session was bound.
|
|
108
|
+
*
|
|
109
|
+
* The SDK opens its root-pump subscription (and legacy lifecycle/values
|
|
110
|
+
* subs) eagerly on thread creation so that no events are missed on fast
|
|
111
|
+
* runs. On WebSocket those subscribes can race ahead of the concurrent
|
|
112
|
+
* `run.start` and hit the service before `ensureRunSession` has bound a
|
|
113
|
+
* session. Rather than rejecting them with `no_such_run`, we park each
|
|
114
|
+
* command's response promise here and resolve it once the first session
|
|
115
|
+
* is bound — mirroring the cross-run `activeSubscriptions` replay path.
|
|
116
|
+
*/
|
|
117
|
+
pendingSubscribes: Array<{
|
|
118
|
+
command: ProtocolCommand;
|
|
119
|
+
resolve: (response: ProtocolSuccess | ProtocolError | null) => void;
|
|
120
|
+
}>;
|
|
121
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/queue.mjs
CHANGED
|
@@ -54,8 +54,14 @@ const worker = async (ops, run, attempt, signal) => {
|
|
|
54
54
|
signal,
|
|
55
55
|
...(!temporary ? { onCheckpoint, onTaskResult } : undefined),
|
|
56
56
|
});
|
|
57
|
-
for await (const { event, data } of stream) {
|
|
58
|
-
await ops.runs.stream.publish({
|
|
57
|
+
for await (const { event, data, normalized } of stream) {
|
|
58
|
+
await ops.runs.stream.publish({
|
|
59
|
+
runId,
|
|
60
|
+
resumable,
|
|
61
|
+
event,
|
|
62
|
+
data,
|
|
63
|
+
...(normalized != null ? { normalized } : {}),
|
|
64
|
+
});
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
catch (error) {
|