@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.
Files changed (52) hide show
  1. package/dist/api/protocol.d.mts +7 -0
  2. package/dist/api/protocol.mjs +157 -0
  3. package/dist/api/runs.mjs +15 -4
  4. package/dist/command.mjs +1 -1
  5. package/dist/experimental/embed/constants.d.mts +3 -0
  6. package/dist/experimental/embed/constants.mjs +11 -0
  7. package/dist/experimental/embed/protocol.d.mts +9 -0
  8. package/dist/experimental/embed/protocol.mjs +453 -0
  9. package/dist/experimental/embed/runs.d.mts +8 -0
  10. package/dist/experimental/embed/runs.mjs +202 -0
  11. package/dist/experimental/embed/threads.d.mts +8 -0
  12. package/dist/experimental/embed/threads.mjs +151 -0
  13. package/dist/experimental/embed/types.d.mts +77 -0
  14. package/dist/experimental/embed/types.mjs +1 -0
  15. package/dist/experimental/embed/utils.d.mts +29 -0
  16. package/dist/experimental/embed/utils.mjs +62 -0
  17. package/dist/experimental/embed.d.mts +11 -31
  18. package/dist/experimental/embed.mjs +19 -399
  19. package/dist/graph/load.d.mts +2 -2
  20. package/dist/graph/load.utils.mjs +13 -3
  21. package/dist/protocol/constants.d.mts +7 -0
  22. package/dist/protocol/constants.mjs +7 -0
  23. package/dist/protocol/service.d.mts +101 -0
  24. package/dist/protocol/service.mjs +568 -0
  25. package/dist/protocol/session/event-normalizers.d.mts +52 -0
  26. package/dist/protocol/session/event-normalizers.mjs +162 -0
  27. package/dist/protocol/session/index.d.mts +261 -0
  28. package/dist/protocol/session/index.mjs +826 -0
  29. package/dist/protocol/session/internal-types.d.mts +67 -0
  30. package/dist/protocol/session/internal-types.mjs +28 -0
  31. package/dist/protocol/session/metadata.d.mts +24 -0
  32. package/dist/protocol/session/metadata.mjs +95 -0
  33. package/dist/protocol/session/namespace.d.mts +47 -0
  34. package/dist/protocol/session/namespace.mjs +62 -0
  35. package/dist/protocol/session/state-normalizers.d.mts +57 -0
  36. package/dist/protocol/session/state-normalizers.mjs +430 -0
  37. package/dist/protocol/session/tool-calls.d.mts +27 -0
  38. package/dist/protocol/session/tool-calls.mjs +59 -0
  39. package/dist/protocol/types.d.mts +121 -0
  40. package/dist/protocol/types.mjs +1 -0
  41. package/dist/queue.mjs +8 -2
  42. package/dist/schemas.d.mts +58 -58
  43. package/dist/semver/index.mjs +19 -1
  44. package/dist/server.mjs +22 -3
  45. package/dist/state.mjs +1 -1
  46. package/dist/storage/ops.mjs +17 -5
  47. package/dist/storage/persist.mjs +1 -1
  48. package/dist/storage/types.d.mts +10 -1
  49. package/dist/stream.d.mts +25 -4
  50. package/dist/stream.mjs +203 -11
  51. package/dist/utils/serde.mjs +1 -1
  52. 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
- function createStubRun(threadId, payload, overrides) {
13
- const now = new Date();
14
- const runId = uuidv7();
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 /threads/:thread_id/stream/events`. 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.post("/threads", zValidator("json", schemas.ThreadCreate), async (c) => {
105
- // create a new thread
106
- const payload = c.req.valid("json");
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
  });
@@ -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
- if (!isCompiledGraph(graph) && "graph" in graph) {
36
- return graph.graph;
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 {};