@langchain/langgraph-api 1.1.17 → 1.2.0
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
|
@@ -1,417 +1,37 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { zValidator } from "@hono/zod-validator";
|
|
3
|
-
import { streamSSE } from "hono/streaming";
|
|
4
|
-
import { v7 as uuidv7 } from "uuid";
|
|
5
|
-
import * as schemas from "../schemas.mjs";
|
|
6
|
-
import { z } from "zod/v3";
|
|
7
|
-
import { streamState } from "../stream.mjs";
|
|
8
|
-
import { serialiseAsDict, serializeError } from "../utils/serde.mjs";
|
|
9
|
-
import { getDisconnectAbortSignal, jsonExtra } from "../utils/hono.mjs";
|
|
10
|
-
import { stateSnapshotToThreadState } from "../state.mjs";
|
|
11
2
|
import { ensureContentType } from "../http/middleware.mjs";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
let streamMode = Array.isArray(payload.stream_mode)
|
|
16
|
-
? payload.stream_mode
|
|
17
|
-
: payload.stream_mode
|
|
18
|
-
? [payload.stream_mode]
|
|
19
|
-
: undefined;
|
|
20
|
-
if (streamMode == null || streamMode.length === 0)
|
|
21
|
-
streamMode = ["values"];
|
|
22
|
-
const config = Object.assign({}, payload.config ?? {}, {
|
|
23
|
-
configurable: {
|
|
24
|
-
run_id: runId,
|
|
25
|
-
thread_id: threadId,
|
|
26
|
-
graph_id: payload.assistant_id,
|
|
27
|
-
...(payload.checkpoint_id
|
|
28
|
-
? { checkpoint_id: payload.checkpoint_id }
|
|
29
|
-
: null),
|
|
30
|
-
...payload.checkpoint,
|
|
31
|
-
...(payload.langsmith_tracer
|
|
32
|
-
? {
|
|
33
|
-
langsmith_project: payload.langsmith_tracer.project_name,
|
|
34
|
-
langsmith_example_id: payload.langsmith_tracer.example_id,
|
|
35
|
-
}
|
|
36
|
-
: null),
|
|
37
|
-
},
|
|
38
|
-
}, { metadata: payload.metadata ?? {} });
|
|
39
|
-
return {
|
|
40
|
-
run_id: runId,
|
|
41
|
-
thread_id: threadId,
|
|
42
|
-
assistant_id: payload.assistant_id,
|
|
43
|
-
metadata: payload.metadata ?? {},
|
|
44
|
-
status: overrides?.status ?? "running",
|
|
45
|
-
kwargs: {
|
|
46
|
-
input: payload.input,
|
|
47
|
-
command: payload.command,
|
|
48
|
-
config,
|
|
49
|
-
context: payload.context,
|
|
50
|
-
stream_mode: streamMode,
|
|
51
|
-
interrupt_before: payload.interrupt_before,
|
|
52
|
-
interrupt_after: payload.interrupt_after,
|
|
53
|
-
feedback_keys: payload.feedback_keys,
|
|
54
|
-
subgraphs: payload.stream_subgraphs,
|
|
55
|
-
temporary: false,
|
|
56
|
-
},
|
|
57
|
-
multitask_strategy: (overrides?.multitask_strategy ??
|
|
58
|
-
payload.multitask_strategy ??
|
|
59
|
-
"reject"),
|
|
60
|
-
created_at: now,
|
|
61
|
-
updated_at: now,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
3
|
+
import { registerThreadRoutes } from "./embed/threads.mjs";
|
|
4
|
+
import { registerRunRoutes } from "./embed/runs.mjs";
|
|
5
|
+
import { registerProtocolRoutes } from "./embed/protocol.mjs";
|
|
64
6
|
/**
|
|
65
7
|
* Create a Hono server with a subset of LangGraph Platform routes.
|
|
66
8
|
*
|
|
9
|
+
* Pass `upgradeWebSocket` to enable the WebSocket protocol transport
|
|
10
|
+
* on `GET /v2/threads/:thread_id/stream`. Create it with `@hono/node-ws`'s
|
|
11
|
+
* `createNodeWebSocket({ app })` and make sure to call `injectWebSocket`
|
|
12
|
+
* on the underlying HTTP server.
|
|
13
|
+
*
|
|
67
14
|
* @experimental Does not follow semver.
|
|
68
15
|
*/
|
|
69
16
|
export function createEmbedServer(options) {
|
|
70
|
-
const threadRunState = new Map();
|
|
71
|
-
function getThreadState(threadId) {
|
|
72
|
-
let state = threadRunState.get(threadId);
|
|
73
|
-
if (!state) {
|
|
74
|
-
state = { activeRunId: null, pendingRuns: [] };
|
|
75
|
-
threadRunState.set(threadId, state);
|
|
76
|
-
}
|
|
77
|
-
return state;
|
|
78
|
-
}
|
|
79
|
-
async function waitForRunReady(threadId, runId, signal) {
|
|
80
|
-
const state = getThreadState(threadId);
|
|
81
|
-
const run = state.pendingRuns.find((r) => r.run_id === runId);
|
|
82
|
-
if (!run)
|
|
83
|
-
return null;
|
|
84
|
-
while (true) {
|
|
85
|
-
if (signal?.aborted) {
|
|
86
|
-
throw new DOMException("Aborted", "AbortError");
|
|
87
|
-
}
|
|
88
|
-
const isHead = state.pendingRuns[0]?.run_id === runId;
|
|
89
|
-
const noActive = !state.activeRunId;
|
|
90
|
-
if (isHead && noActive)
|
|
91
|
-
break;
|
|
92
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
93
|
-
}
|
|
94
|
-
return run;
|
|
95
|
-
}
|
|
96
17
|
async function getGraph(graphId) {
|
|
97
|
-
const targetGraph = options.graph[graphId];
|
|
18
|
+
const targetGraph = await options.graph[graphId];
|
|
98
19
|
targetGraph.store = options.store;
|
|
99
20
|
targetGraph.checkpointer = options.checkpointer;
|
|
100
21
|
return targetGraph;
|
|
101
22
|
}
|
|
23
|
+
const context = {
|
|
24
|
+
graph: options.graph,
|
|
25
|
+
threads: options.threads,
|
|
26
|
+
checkpointer: options.checkpointer,
|
|
27
|
+
store: options.store,
|
|
28
|
+
getGraph,
|
|
29
|
+
};
|
|
102
30
|
const api = new Hono();
|
|
103
31
|
api.use(ensureContentType());
|
|
104
|
-
api
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const threadId = payload.thread_id || uuidv7();
|
|
108
|
-
return jsonExtra(c, await options.threads.set(threadId, {
|
|
109
|
-
kind: "put",
|
|
110
|
-
metadata: payload.metadata,
|
|
111
|
-
}));
|
|
112
|
-
});
|
|
113
|
-
api.get("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), async (c) => {
|
|
114
|
-
// Get Thread
|
|
115
|
-
const { thread_id } = c.req.valid("param");
|
|
116
|
-
return jsonExtra(c, await options.threads.get(thread_id));
|
|
117
|
-
});
|
|
118
|
-
api.patch("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.ThreadCreate), async (c) => {
|
|
119
|
-
// Update Thread
|
|
120
|
-
const { thread_id } = c.req.valid("param");
|
|
121
|
-
const payload = c.req.valid("json");
|
|
122
|
-
return jsonExtra(c, await options.threads.set(thread_id, {
|
|
123
|
-
kind: "patch",
|
|
124
|
-
metadata: payload.metadata,
|
|
125
|
-
}));
|
|
126
|
-
});
|
|
127
|
-
api.delete("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), async (c) => {
|
|
128
|
-
// Delete Thread
|
|
129
|
-
const { thread_id } = c.req.valid("param");
|
|
130
|
-
await options.threads.delete(thread_id);
|
|
131
|
-
return new Response(null, { status: 204 });
|
|
132
|
-
});
|
|
133
|
-
api.post("/threads/search", zValidator("json", schemas.ThreadSearchRequest), async (c) => {
|
|
134
|
-
const payload = c.req.valid("json");
|
|
135
|
-
const result = [];
|
|
136
|
-
if (!options.threads.search)
|
|
137
|
-
return c.json({ error: "Threads search not implemented" }, 422);
|
|
138
|
-
const sortBy = payload.sort_by === "created_at" || payload.sort_by === "updated_at"
|
|
139
|
-
? payload.sort_by
|
|
140
|
-
: "created_at";
|
|
141
|
-
let total = 0;
|
|
142
|
-
for await (const item of options.threads.search({
|
|
143
|
-
metadata: payload.metadata,
|
|
144
|
-
limit: payload.limit ?? 10,
|
|
145
|
-
offset: payload.offset ?? 0,
|
|
146
|
-
sortBy,
|
|
147
|
-
sortOrder: payload.sort_order ?? "desc",
|
|
148
|
-
})) {
|
|
149
|
-
result.push(item.thread);
|
|
150
|
-
// Only set total if it's the first item
|
|
151
|
-
if (total === 0)
|
|
152
|
-
total = item.total;
|
|
153
|
-
}
|
|
154
|
-
c.res.headers.set("X-Pagination-Total", total.toString());
|
|
155
|
-
return jsonExtra(c, result);
|
|
156
|
-
});
|
|
157
|
-
api.get("/threads/:thread_id/state", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("query", z.object({ subgraphs: schemas.coercedBoolean.optional() })), async (c) => {
|
|
158
|
-
// Get Latest Thread State
|
|
159
|
-
const { thread_id } = c.req.valid("param");
|
|
160
|
-
const { subgraphs } = c.req.valid("query");
|
|
161
|
-
const thread = await options.threads.get(thread_id);
|
|
162
|
-
const graphId = thread.metadata?.graph_id;
|
|
163
|
-
const graph = graphId ? await getGraph(graphId) : undefined;
|
|
164
|
-
if (graph == null) {
|
|
165
|
-
return jsonExtra(c, stateSnapshotToThreadState({
|
|
166
|
-
values: {},
|
|
167
|
-
next: [],
|
|
168
|
-
config: {},
|
|
169
|
-
metadata: undefined,
|
|
170
|
-
createdAt: undefined,
|
|
171
|
-
parentConfig: undefined,
|
|
172
|
-
tasks: [],
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
const config = { configurable: { thread_id } };
|
|
176
|
-
const result = await graph.getState(config, { subgraphs });
|
|
177
|
-
return jsonExtra(c, stateSnapshotToThreadState(result));
|
|
178
|
-
});
|
|
179
|
-
api.post("/threads/:thread_id/state", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.ThreadStateUpdate), async (c) => {
|
|
180
|
-
// Update Thread State
|
|
181
|
-
const { thread_id } = c.req.valid("param");
|
|
182
|
-
const payload = c.req.valid("json");
|
|
183
|
-
const config = { configurable: { thread_id } };
|
|
184
|
-
config.configurable ??= {};
|
|
185
|
-
if (payload.checkpoint_id) {
|
|
186
|
-
config.configurable.checkpoint_id = payload.checkpoint_id;
|
|
187
|
-
}
|
|
188
|
-
if (payload.checkpoint) {
|
|
189
|
-
Object.assign(config.configurable, payload.checkpoint);
|
|
190
|
-
}
|
|
191
|
-
const thread = await options.threads.get(thread_id);
|
|
192
|
-
const graphId = thread.metadata?.graph_id;
|
|
193
|
-
const graph = graphId ? await getGraph(graphId) : undefined;
|
|
194
|
-
if (graph == null)
|
|
195
|
-
return c.json({ error: "Graph not found" }, 404);
|
|
196
|
-
const result = await graph.updateState(config, payload.values, payload.as_node);
|
|
197
|
-
return jsonExtra(c, { checkpoint: result.configurable });
|
|
198
|
-
});
|
|
199
|
-
// get thread state at checkpoint
|
|
200
|
-
api.get("/threads/:thread_id/state/:checkpoint_id", zValidator("param", z.object({
|
|
201
|
-
thread_id: z.string().uuid(),
|
|
202
|
-
checkpoint_id: z.string().uuid(),
|
|
203
|
-
})), zValidator("query", z.object({ subgraphs: schemas.coercedBoolean.optional() })), async (c) => {
|
|
204
|
-
// Get Thread State At Checkpoint
|
|
205
|
-
const { thread_id, checkpoint_id } = c.req.valid("param");
|
|
206
|
-
const { subgraphs } = c.req.valid("query");
|
|
207
|
-
const thread = await options.threads.get(thread_id);
|
|
208
|
-
const graphId = thread.metadata?.graph_id;
|
|
209
|
-
const graph = graphId ? await getGraph(graphId) : undefined;
|
|
210
|
-
if (graph == null)
|
|
211
|
-
return c.json({ error: "Graph not found" }, 404);
|
|
212
|
-
const result = await graph.getState({ configurable: { thread_id, checkpoint_id } }, { subgraphs });
|
|
213
|
-
return jsonExtra(c, stateSnapshotToThreadState(result));
|
|
214
|
-
});
|
|
215
|
-
api.post("/threads/:thread_id/state/checkpoint", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({
|
|
216
|
-
subgraphs: schemas.coercedBoolean.optional(),
|
|
217
|
-
checkpoint: schemas.CheckpointSchema.nullish(),
|
|
218
|
-
})), async (c) => {
|
|
219
|
-
// Get Thread State At Checkpoint post
|
|
220
|
-
const { thread_id } = c.req.valid("param");
|
|
221
|
-
const { checkpoint, subgraphs } = c.req.valid("json");
|
|
222
|
-
const thread = await options.threads.get(thread_id);
|
|
223
|
-
const graphId = thread.metadata?.graph_id;
|
|
224
|
-
const graph = graphId ? await getGraph(graphId) : undefined;
|
|
225
|
-
if (graph == null)
|
|
226
|
-
return c.json({ error: "Graph not found" }, 404);
|
|
227
|
-
const result = await graph.getState({ configurable: { thread_id, ...checkpoint } }, { subgraphs });
|
|
228
|
-
return jsonExtra(c, stateSnapshotToThreadState(result));
|
|
229
|
-
});
|
|
230
|
-
api.post("/threads/:thread_id/history", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.ThreadHistoryRequest), async (c) => {
|
|
231
|
-
// Get Thread History Post
|
|
232
|
-
const { thread_id } = c.req.valid("param");
|
|
233
|
-
const { limit, before, metadata, checkpoint } = c.req.valid("json");
|
|
234
|
-
const thread = await options.threads.get(thread_id);
|
|
235
|
-
const graphId = thread.metadata?.graph_id;
|
|
236
|
-
const graph = graphId ? await getGraph(graphId) : undefined;
|
|
237
|
-
if (graph == null)
|
|
238
|
-
return jsonExtra(c, []);
|
|
239
|
-
const config = { configurable: { thread_id, ...checkpoint } };
|
|
240
|
-
const result = [];
|
|
241
|
-
const beforeConfig = typeof before === "string"
|
|
242
|
-
? { configurable: { checkpoint_id: before } }
|
|
243
|
-
: before;
|
|
244
|
-
for await (const state of graph.getStateHistory(config, {
|
|
245
|
-
limit,
|
|
246
|
-
before: beforeConfig,
|
|
247
|
-
filter: metadata,
|
|
248
|
-
})) {
|
|
249
|
-
result.push(stateSnapshotToThreadState(state));
|
|
250
|
-
}
|
|
251
|
-
return jsonExtra(c, result);
|
|
252
|
-
});
|
|
253
|
-
api.post("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
|
|
254
|
-
const { thread_id } = c.req.valid("param");
|
|
255
|
-
const payload = c.req.valid("json");
|
|
256
|
-
const thread = await options.threads.get(thread_id);
|
|
257
|
-
if (thread == null)
|
|
258
|
-
return c.json({ error: "Thread not found" }, 404);
|
|
259
|
-
const state = getThreadState(thread_id);
|
|
260
|
-
const multitaskStrategy = payload.multitask_strategy ?? "reject";
|
|
261
|
-
const shouldEnqueue = multitaskStrategy === "enqueue" && state.activeRunId != null;
|
|
262
|
-
const run = createStubRun(thread_id, payload, {
|
|
263
|
-
status: shouldEnqueue ? "pending" : "running",
|
|
264
|
-
multitask_strategy: multitaskStrategy,
|
|
265
|
-
});
|
|
266
|
-
state.pendingRuns.push(run);
|
|
267
|
-
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
268
|
-
return jsonExtra(c, run);
|
|
269
|
-
});
|
|
270
|
-
api.get("/threads/:thread_id/runs/:run_id/stream", zValidator("param", z.object({
|
|
271
|
-
thread_id: z.string().uuid(),
|
|
272
|
-
run_id: z.string().uuid(),
|
|
273
|
-
})), async (c) => {
|
|
274
|
-
const { thread_id, run_id } = c.req.valid("param");
|
|
275
|
-
const thread = await options.threads.get(thread_id);
|
|
276
|
-
if (thread == null)
|
|
277
|
-
return c.json({ error: "Thread not found" }, 404);
|
|
278
|
-
return streamSSE(c, async (stream) => {
|
|
279
|
-
const signal = getDisconnectAbortSignal(c, stream);
|
|
280
|
-
const state = getThreadState(thread_id);
|
|
281
|
-
try {
|
|
282
|
-
const run = await waitForRunReady(thread_id, run_id, signal);
|
|
283
|
-
if (!run) {
|
|
284
|
-
await stream.writeSSE({
|
|
285
|
-
data: serialiseAsDict({ error: "Run not found" }),
|
|
286
|
-
event: "error",
|
|
287
|
-
});
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const idx = state.pendingRuns.findIndex((r) => r.run_id === run_id);
|
|
291
|
-
if (idx >= 0)
|
|
292
|
-
state.pendingRuns.splice(idx, 1);
|
|
293
|
-
state.activeRunId = run_id;
|
|
294
|
-
run.status = "running";
|
|
295
|
-
try {
|
|
296
|
-
for await (const { event, data } of streamState(run, {
|
|
297
|
-
attempt: 1,
|
|
298
|
-
getGraph,
|
|
299
|
-
signal,
|
|
300
|
-
})) {
|
|
301
|
-
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
await stream.writeSSE({
|
|
306
|
-
data: serialiseAsDict(serializeError(error)),
|
|
307
|
-
event: "error",
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
finally {
|
|
311
|
-
if (state.activeRunId === run_id) {
|
|
312
|
-
state.activeRunId = null;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
throw err;
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
api.post("/threads/:thread_id/runs/:run_id/cancel", zValidator("param", z.object({
|
|
325
|
-
thread_id: z.string().uuid(),
|
|
326
|
-
run_id: z.string().uuid(),
|
|
327
|
-
})), async (c) => {
|
|
328
|
-
const { thread_id, run_id } = c.req.valid("param");
|
|
329
|
-
const state = getThreadState(thread_id);
|
|
330
|
-
const idx = state.pendingRuns.findIndex((r) => r.run_id === run_id);
|
|
331
|
-
if (idx >= 0) {
|
|
332
|
-
state.pendingRuns.splice(idx, 1);
|
|
333
|
-
}
|
|
334
|
-
return new Response(null, { status: 204 });
|
|
335
|
-
});
|
|
336
|
-
api.post("/threads/:thread_id/runs/stream", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
|
|
337
|
-
// Stream Run (create + stream in one request)
|
|
338
|
-
const { thread_id } = c.req.valid("param");
|
|
339
|
-
const payload = c.req.valid("json");
|
|
340
|
-
const thread = await options.threads.get(thread_id);
|
|
341
|
-
if (thread == null)
|
|
342
|
-
return c.json({ error: "Thread not found" }, 404);
|
|
343
|
-
const state = getThreadState(thread_id);
|
|
344
|
-
const run = createStubRun(thread_id, payload);
|
|
345
|
-
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
346
|
-
return streamSSE(c, async (stream) => {
|
|
347
|
-
const signal = getDisconnectAbortSignal(c, stream);
|
|
348
|
-
state.activeRunId = run.run_id;
|
|
349
|
-
await options.threads.set(thread_id, {
|
|
350
|
-
kind: "patch",
|
|
351
|
-
metadata: {
|
|
352
|
-
graph_id: payload.assistant_id,
|
|
353
|
-
assistant_id: payload.assistant_id,
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
try {
|
|
357
|
-
for await (const { event, data } of streamState(run, {
|
|
358
|
-
attempt: 1,
|
|
359
|
-
getGraph,
|
|
360
|
-
signal,
|
|
361
|
-
})) {
|
|
362
|
-
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
catch (error) {
|
|
366
|
-
await stream.writeSSE({
|
|
367
|
-
data: serialiseAsDict(serializeError(error)),
|
|
368
|
-
event: "error",
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
if (state.activeRunId === run.run_id) {
|
|
373
|
-
state.activeRunId = null;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
|
|
379
|
-
// Stream Stateless Run
|
|
380
|
-
const payload = c.req.valid("json");
|
|
381
|
-
const threadId = uuidv7();
|
|
382
|
-
const run = createStubRun(threadId, payload);
|
|
383
|
-
c.header("Content-Location", `/threads/${threadId}/runs/${run.run_id}`);
|
|
384
|
-
return streamSSE(c, async (stream) => {
|
|
385
|
-
const signal = getDisconnectAbortSignal(c, stream);
|
|
386
|
-
await options.threads.set(threadId, {
|
|
387
|
-
kind: "put",
|
|
388
|
-
metadata: {
|
|
389
|
-
graph_id: payload.assistant_id,
|
|
390
|
-
assistant_id: payload.assistant_id,
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
try {
|
|
394
|
-
try {
|
|
395
|
-
for await (const { event, data } of streamState(run, {
|
|
396
|
-
attempt: 1,
|
|
397
|
-
getGraph,
|
|
398
|
-
signal,
|
|
399
|
-
})) {
|
|
400
|
-
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
await stream.writeSSE({
|
|
405
|
-
data: serialiseAsDict(serializeError(error)),
|
|
406
|
-
event: "error",
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
finally {
|
|
411
|
-
await options.threads.delete(threadId);
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
});
|
|
32
|
+
registerThreadRoutes(api, context);
|
|
33
|
+
registerRunRoutes(api, context);
|
|
34
|
+
registerProtocolRoutes(api, context, options.upgradeWebSocket);
|
|
415
35
|
api.notFound((c) => {
|
|
416
36
|
return c.json({ error: `${c.req.method} ${c.req.path} not implemented` }, 404);
|
|
417
37
|
});
|
package/dist/graph/load.d.mts
CHANGED
|
@@ -9,11 +9,11 @@ export declare const NAMESPACE_GRAPH: Uint8Array<ArrayBufferLike>;
|
|
|
9
9
|
export declare const getAssistantId: (graphId: string) => string;
|
|
10
10
|
export declare function registerFromEnv(assistants: AssistantsRepo, specs: Record<string, string>, options: {
|
|
11
11
|
cwd: string;
|
|
12
|
-
}): Promise<(CompiledGraph<string, any, any, Record<string, any>, any, any, unknown, unknown, any> | CompiledGraphFactory<string>)[]>;
|
|
12
|
+
}): Promise<(CompiledGraph<string, any, any, Record<string, any>, any, any, unknown, unknown, any, []> | CompiledGraphFactory<string>)[]>;
|
|
13
13
|
export declare function getGraph(graphId: string, config: LangGraphRunnableConfig | undefined, options?: {
|
|
14
14
|
checkpointer?: BaseCheckpointSaver | null;
|
|
15
15
|
store?: BaseStore;
|
|
16
|
-
}): Promise<CompiledGraph<string, any, any, Record<string, any>, any, any, unknown, unknown, any>>;
|
|
16
|
+
}): Promise<CompiledGraph<string, any, any, Record<string, any>, any, any, unknown, unknown, any, []>>;
|
|
17
17
|
export declare function assertGraphExists(graphId: string): void;
|
|
18
18
|
export declare function getGraphKeys(): string[];
|
|
19
19
|
export declare function getCachedStaticGraphSchema(graphId: string): Promise<Record<string, GraphSchema>>;
|
|
@@ -31,9 +31,19 @@ export async function resolveGraph(spec, options) {
|
|
|
31
31
|
throw new Error("Failed to load graph: graph is nullush");
|
|
32
32
|
const afterResolve = (graphLike) => {
|
|
33
33
|
const graph = isGraph(graphLike) ? graphLike.compile() : graphLike;
|
|
34
|
-
// TODO: hack, remove once LangChain 1.x createAgent is fixed
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// TODO: hack, remove once LangChain 1.x createAgent is fixed.
|
|
35
|
+
// `createAgent` returns a ReactAgent wrapper that itself looks
|
|
36
|
+
// like a CompiledGraph (it has a `builder` — the outer
|
|
37
|
+
// StateGraph) *and* exposes the real compiled pregel under
|
|
38
|
+
// `.graph`. Unwrap to the inner graph whenever both are
|
|
39
|
+
// present so downstream code (e.g. the v2 streaming path that
|
|
40
|
+
// keys off `graph.streamTransformers`) sees the actual pregel
|
|
41
|
+
// rather than the wrapper.
|
|
42
|
+
const inner = graph.graph;
|
|
43
|
+
if (inner != null &&
|
|
44
|
+
typeof inner === "object" &&
|
|
45
|
+
isCompiledGraph(inner)) {
|
|
46
|
+
return inner;
|
|
37
47
|
}
|
|
38
48
|
return graph;
|
|
39
49
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal kwargs flag set on runs created through the protocol transport.
|
|
3
|
+
*
|
|
4
|
+
* When present, the streaming layer uses `streamStateV2` and forwards native
|
|
5
|
+
* protocol events to the v2 protocol session.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PROTOCOL_STREAM_RUN_KEY = "__protocol_stream_run";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal kwargs flag set on runs created through the protocol transport.
|
|
3
|
+
*
|
|
4
|
+
* When present, the streaming layer uses `streamStateV2` and forwards native
|
|
5
|
+
* protocol events to the v2 protocol session.
|
|
6
|
+
*/
|
|
7
|
+
export const PROTOCOL_STREAM_RUN_KEY = "__protocol_stream_run";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { RunsRepo, ThreadsRepo } from "../storage/types.mjs";
|
|
2
|
+
import type { EventSinkEntry, EventSinkFilter, ProtocolCommand, ProtocolError, ProtocolEvent, ProtocolSuccess, ThreadRecord, ProtocolTransportName } from "./types.mjs";
|
|
3
|
+
type ServiceBindings = {
|
|
4
|
+
runs: RunsRepo;
|
|
5
|
+
threads: ThreadsRepo;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Transport-agnostic sink used to forward normalized protocol events into a
|
|
9
|
+
* concrete delivery mechanism such as WebSocket or SSE.
|
|
10
|
+
*/
|
|
11
|
+
type EventSink = (message: ProtocolEvent) => Promise<void> | void;
|
|
12
|
+
/**
|
|
13
|
+
* Thread-scoped connection registry and command dispatcher.
|
|
14
|
+
*
|
|
15
|
+
* In the thread-centric protocol, a `ThreadRecord` holds ephemeral
|
|
16
|
+
* connection state for an active client interacting with a thread. The
|
|
17
|
+
* thread itself is durable (lives in the checkpoint store); records are
|
|
18
|
+
* created lazily on first interaction and dropped when all connections
|
|
19
|
+
* close.
|
|
20
|
+
*/
|
|
21
|
+
export declare class ProtocolService {
|
|
22
|
+
private readonly bindings;
|
|
23
|
+
private readonly threads;
|
|
24
|
+
constructor(bindings: ServiceBindings);
|
|
25
|
+
getThread(threadId: string): ThreadRecord | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Get or create the in-memory record for a thread. Records hold
|
|
28
|
+
* ephemeral connection state (event sinks, current run session) and
|
|
29
|
+
* are created on first use for any thread the client targets.
|
|
30
|
+
*/
|
|
31
|
+
ensureThread(options: {
|
|
32
|
+
threadId: string;
|
|
33
|
+
transport: ProtocolTransportName;
|
|
34
|
+
auth?: ThreadRecord["auth"];
|
|
35
|
+
sendEvent?: EventSink;
|
|
36
|
+
}): ThreadRecord;
|
|
37
|
+
/**
|
|
38
|
+
* Attach a live transport consumer (WebSocket) and flush any buffered
|
|
39
|
+
* events.
|
|
40
|
+
*/
|
|
41
|
+
attachEventSink(threadId: string, sendEvent: EventSink): Promise<ThreadRecord>;
|
|
42
|
+
/**
|
|
43
|
+
* Attach a filtered SSE event sink and replay buffered events that
|
|
44
|
+
* match the filter.
|
|
45
|
+
*
|
|
46
|
+
* The sink is flagged `pendingReplay` while draining so that the live
|
|
47
|
+
* `send` path skips it — preventing live events from interleaving with
|
|
48
|
+
* the replay loop's awaits and producing out-of-order delivery.
|
|
49
|
+
*/
|
|
50
|
+
attachFilteredEventSink(threadId: string, sink: EventSinkEntry): Promise<ThreadRecord>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove an SSE event sink when the connection closes.
|
|
53
|
+
*/
|
|
54
|
+
detachEventSink(threadId: string, sinkId: string): void;
|
|
55
|
+
closeThread(threadId: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Route a protocol command on a thread.
|
|
58
|
+
*
|
|
59
|
+
* `subscription.subscribe` can resolve to `null` on ordered
|
|
60
|
+
* transports (WebSocket) — see
|
|
61
|
+
* `ProtocolSession.handleSubscribeForResponse` for the rationale.
|
|
62
|
+
* Callers must treat `null` as "response already sent on the wire"
|
|
63
|
+
* and skip any additional send.
|
|
64
|
+
*/
|
|
65
|
+
handleCommand(threadId: string, command: ProtocolCommand): Promise<ProtocolSuccess | ProtocolError | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Start a new run, resume an interrupted run, or continue on the
|
|
68
|
+
* thread depending on its current state.
|
|
69
|
+
*/
|
|
70
|
+
private handleRunStart;
|
|
71
|
+
private handleInputRespond;
|
|
72
|
+
private createOrResumeRun;
|
|
73
|
+
private hasPendingInterrupts;
|
|
74
|
+
private hasPendingInterruptsForThread;
|
|
75
|
+
private handleStateGet;
|
|
76
|
+
private forwardToRunSession;
|
|
77
|
+
/**
|
|
78
|
+
* Drain any WebSocket subscribes that arrived before the first run
|
|
79
|
+
* session was bound. Called from `createOrResumeRun` right after
|
|
80
|
+
* {@link ensureRunSession} sets up `record.session`.
|
|
81
|
+
*
|
|
82
|
+
* Each parked command is forwarded to the freshly-bound session,
|
|
83
|
+
* added to `activeSubscriptions` so it persists across subsequent
|
|
84
|
+
* runs, and its deferred `handleCommand` promise is resolved so the
|
|
85
|
+
* WebSocket handler can finally send the response.
|
|
86
|
+
*/
|
|
87
|
+
private drainPendingSubscribes;
|
|
88
|
+
/**
|
|
89
|
+
* Bind the thread record to a concrete LangGraph run and forward
|
|
90
|
+
* normalized protocol events to attached sinks.
|
|
91
|
+
*/
|
|
92
|
+
private ensureRunSession;
|
|
93
|
+
private requireThread;
|
|
94
|
+
error(id: number | null, code: ProtocolError["error"], message: string): ProtocolError;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check whether a protocol event matches an SSE event sink filter.
|
|
98
|
+
* Mirrors the subscription matching logic in {@link RunProtocolSession}.
|
|
99
|
+
*/
|
|
100
|
+
export declare function matchesSinkFilter(filter: EventSinkFilter, event: ProtocolEvent): boolean;
|
|
101
|
+
export {};
|