@langchain/langgraph-api 1.2.5 → 1.3.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.
@@ -103,7 +103,11 @@ export declare class ProtocolService {
103
103
  }
104
104
  /**
105
105
  * Check whether a protocol event matches an SSE event sink filter.
106
- * Mirrors the subscription matching logic in {@link RunProtocolSession}.
106
+ *
107
+ * Shares its channel inference and namespace matching with
108
+ * {@link RunProtocolSession} (and the core streaming toolkit) so SSE and
109
+ * WebSocket subscribers apply identical semantics, including dynamic
110
+ * namespace-suffix normalization.
107
111
  */
108
112
  export declare function matchesSinkFilter(filter: EventSinkFilter, event: ProtocolEvent): boolean;
109
113
  export {};
@@ -1,6 +1,6 @@
1
+ import { inferChannel, isPrefixMatch } from "@langchain/langgraph/stream";
1
2
  import { v7 as uuid7 } from "uuid";
2
3
  import { getAssistantId } from "../graph/load.mjs";
3
- import { isSupportedChannel, isRecord as isRecordInternal, } from "./session/internal-types.mjs";
4
4
  import { PROTOCOL_STREAM_RUN_KEY } from "./constants.mjs";
5
5
  import { RunProtocolSession } from "./session/index.mjs";
6
6
  const DEFAULT_RUN_STREAM_MODES = [
@@ -564,35 +564,24 @@ export class ProtocolService {
564
564
  };
565
565
  }
566
566
  }
567
- function isPrefixMatch(namespace, prefix) {
568
- if (prefix.length > namespace.length)
569
- return false;
570
- return prefix.every((segment, i) => namespace[i] === segment);
571
- }
572
567
  /**
573
568
  * Check whether a protocol event matches an SSE event sink filter.
574
- * Mirrors the subscription matching logic in {@link RunProtocolSession}.
569
+ *
570
+ * Shares its channel inference and namespace matching with
571
+ * {@link RunProtocolSession} (and the core streaming toolkit) so SSE and
572
+ * WebSocket subscribers apply identical semantics, including dynamic
573
+ * namespace-suffix normalization.
575
574
  */
576
575
  export function matchesSinkFilter(filter, event) {
577
576
  if (filter.since != null && (event.seq ?? 0) <= filter.since)
578
577
  return false;
579
- const channel = event.method === "input.requested"
580
- ? "input"
581
- : isSupportedChannel(event.method)
582
- ? event.method
583
- : undefined;
578
+ // `inferChannel` resolves named custom channels to `custom:<name>`; a bare
579
+ // `custom` filter still matches via the prefix check below.
580
+ const channel = inferChannel(event);
584
581
  if (channel == null)
585
582
  return false;
586
- let channelMatched = filter.channels.has(channel);
587
- if (!channelMatched && channel === "custom") {
588
- const params = event.params;
589
- const eventName = isRecordInternal(params.data) && typeof params.data.name === "string"
590
- ? params.data.name
591
- : undefined;
592
- if (eventName != null) {
593
- channelMatched = filter.channels.has(`custom:${eventName}`);
594
- }
595
- }
583
+ const channelMatched = filter.channels.has(channel) ||
584
+ (channel.startsWith("custom:") && filter.channels.has("custom"));
596
585
  if (!channelMatched)
597
586
  return false;
598
587
  if (filter.namespaces == null || filter.namespaces.length === 0) {
@@ -1,7 +1,8 @@
1
+ import { inferChannel, isSupportedChannel } from "@langchain/langgraph/stream";
1
2
  import { v7 as uuid7 } from "uuid";
2
3
  import { serialiseAsDict, serializeError } from "../../utils/serde.mjs";
3
4
  import { normalizeInputRequestedData, normalizeToolData, stripInterruptsFromValues, toLifecycleStatus, } from "./event-normalizers.mjs";
4
- import { SUPPORTED_CHANNELS, isRecord, isSupportedChannel, } from "./internal-types.mjs";
5
+ import { isRecord } from "./internal-types.mjs";
5
6
  import { guessGraphName, isPrefixMatch, normalizeNamespace, parseEventName, toNamespaceKey, } from "./namespace.mjs";
6
7
  import { normalizeProtocolStatePayload } from "./state-normalizers.mjs";
7
8
  /**
@@ -490,25 +491,13 @@ export class RunProtocolSession {
490
491
  * @returns Whether the event matches channel and namespace filters.
491
492
  */
492
493
  matchesSubscription(subscription, event) {
493
- const channel = event.method === "input.requested"
494
- ? "input"
495
- : isSupportedChannel(event.method)
496
- ? event.method
497
- : undefined;
494
+ // `inferChannel` resolves named custom channels to `custom:<name>`; a bare
495
+ // `custom` subscription still matches via the prefix check below.
496
+ const channel = inferChannel(event);
498
497
  if (channel == null)
499
498
  return false;
500
- // Support "custom:name" subscriptions: "custom:a2a" matches custom
501
- // events with data.name === "a2a", while "custom" matches all.
502
- let channelMatched = subscription.channels.has(channel);
503
- if (!channelMatched && channel === "custom") {
504
- const params = event.params;
505
- const eventName = isRecord(params.data) && typeof params.data.name === "string"
506
- ? params.data.name
507
- : undefined;
508
- if (eventName != null) {
509
- channelMatched = subscription.channels.has(`custom:${eventName}`);
510
- }
511
- }
499
+ const channelMatched = subscription.channels.has(channel) ||
500
+ (channel.startsWith("custom:") && subscription.channels.has("custom"));
512
501
  if (!channelMatched)
513
502
  return false;
514
503
  if (subscription.namespaces == null ||
@@ -580,9 +569,7 @@ export class RunProtocolSession {
580
569
  await this.sendError(command.id, "invalid_argument", "subscription.subscribe requires a non-empty channels array.");
581
570
  return;
582
571
  }
583
- const channels = rawChannels.filter((value) => typeof value === "string" &&
584
- (SUPPORTED_CHANNELS.has(value) ||
585
- value.startsWith("custom:")));
572
+ const channels = rawChannels.filter((value) => typeof value === "string" && isSupportedChannel(value));
586
573
  if (channels.length !== rawChannels.length) {
587
574
  await this.sendError(command.id, "invalid_argument", "subscription.subscribe received an unsupported channel.");
588
575
  return;
@@ -666,9 +653,7 @@ export class RunProtocolSession {
666
653
  if (!Array.isArray(rawChannels) || rawChannels.length === 0) {
667
654
  return this.error(command.id, "invalid_argument", "subscription.subscribe requires a non-empty channels array.", meta);
668
655
  }
669
- const channels = rawChannels.filter((value) => typeof value === "string" &&
670
- (SUPPORTED_CHANNELS.has(value) ||
671
- value.startsWith("custom:")));
656
+ const channels = rawChannels.filter((value) => typeof value === "string" && isSupportedChannel(value));
672
657
  if (channels.length !== rawChannels.length) {
673
658
  return this.error(command.id, "invalid_argument", "subscription.subscribe received an unsupported channel.", meta);
674
659
  }
@@ -47,10 +47,6 @@ export type ProtocolEventDataMap = {
47
47
  input: ProtocolEventByMethod<"input">["params"]["data"];
48
48
  tasks: ProtocolEventByMethod<"tasks">["params"]["data"];
49
49
  };
50
- /**
51
- * Channel names supported by the run-scoped protocol session.
52
- */
53
- export declare const SUPPORTED_CHANNELS: Set<SupportedChannel>;
54
50
  /**
55
51
  * Checks whether a value is a non-null object record.
56
52
  *
@@ -58,10 +54,3 @@ export declare const SUPPORTED_CHANNELS: Set<SupportedChannel>;
58
54
  * @returns Whether the value can be treated as a keyed object.
59
55
  */
60
56
  export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
61
- /**
62
- * Checks whether a channel name is supported by the session transport.
63
- *
64
- * @param value - Raw channel name.
65
- * @returns Whether the name matches a supported protocol channel.
66
- */
67
- export declare const isSupportedChannel: (value: string) => value is SupportedChannel;
@@ -1,17 +1,3 @@
1
- /**
2
- * Channel names supported by the run-scoped protocol session.
3
- */
4
- export const SUPPORTED_CHANNELS = new Set([
5
- "values",
6
- "updates",
7
- "checkpoints",
8
- "messages",
9
- "tools",
10
- "custom",
11
- "lifecycle",
12
- "input",
13
- "tasks",
14
- ]);
15
1
  /**
16
2
  * Checks whether a value is a non-null object record.
17
3
  *
@@ -19,10 +5,3 @@ export const SUPPORTED_CHANNELS = new Set([
19
5
  * @returns Whether the value can be treated as a keyed object.
20
6
  */
21
7
  export const isRecord = (value) => typeof value === "object" && value !== null;
22
- /**
23
- * Checks whether a channel name is supported by the session transport.
24
- *
25
- * @param value - Raw channel name.
26
- * @returns Whether the name matches a supported protocol channel.
27
- */
28
- export const isSupportedChannel = (value) => SUPPORTED_CHANNELS.has(value);
@@ -1,4 +1,11 @@
1
+ import { isPrefixMatch, normalizeNamespaceSegment } from "@langchain/langgraph/stream";
1
2
  import type { Namespace } from "../types.mjs";
3
+ /**
4
+ * Namespace prefix matching and dynamic-suffix normalization are shared with
5
+ * the core streaming toolkit so the server, the SDK, and custom transports all
6
+ * agree on subscription semantics.
7
+ */
8
+ export { isPrefixMatch, normalizeNamespaceSegment };
2
9
  /**
3
10
  * Converts a namespace array into a stable internal lookup key.
4
11
  *
@@ -6,13 +13,6 @@ import type { Namespace } from "../types.mjs";
6
13
  * @returns A string key that preserves segment boundaries.
7
14
  */
8
15
  export declare const toNamespaceKey: (namespace: Namespace) => string;
9
- /**
10
- * Strips dynamic suffixes from a namespace segment for display purposes.
11
- *
12
- * @param segment - Raw namespace segment.
13
- * @returns The stable graph-oriented portion of the segment.
14
- */
15
- export declare const normalizeNamespaceSegment: (segment: string) => string;
16
16
  /**
17
17
  * Preserves raw namespace segments in protocol events.
18
18
  *
@@ -30,14 +30,6 @@ export declare const parseEventName: (event: string) => {
30
30
  method: string;
31
31
  namespace: string[];
32
32
  };
33
- /**
34
- * Checks whether a namespace starts with a requested prefix.
35
- *
36
- * @param namespace - Event namespace to test.
37
- * @param prefix - Subscription namespace prefix.
38
- * @returns Whether the namespace matches the prefix semantics.
39
- */
40
- export declare const isPrefixMatch: (namespace: Namespace, prefix: Namespace) => boolean;
41
33
  /**
42
34
  * Guesses a human-readable graph name from a namespace.
43
35
  *
@@ -1,3 +1,10 @@
1
+ import { isPrefixMatch, normalizeNamespaceSegment, } from "@langchain/langgraph/stream";
2
+ /**
3
+ * Namespace prefix matching and dynamic-suffix normalization are shared with
4
+ * the core streaming toolkit so the server, the SDK, and custom transports all
5
+ * agree on subscription semantics.
6
+ */
7
+ export { isPrefixMatch, normalizeNamespaceSegment };
1
8
  /**
2
9
  * Converts a namespace array into a stable internal lookup key.
3
10
  *
@@ -5,13 +12,6 @@
5
12
  * @returns A string key that preserves segment boundaries.
6
13
  */
7
14
  export const toNamespaceKey = (namespace) => namespace.join("\0");
8
- /**
9
- * Strips dynamic suffixes from a namespace segment for display purposes.
10
- *
11
- * @param segment - Raw namespace segment.
12
- * @returns The stable graph-oriented portion of the segment.
13
- */
14
- export const normalizeNamespaceSegment = (segment) => segment.split(":")[0];
15
15
  /**
16
16
  * Preserves raw namespace segments in protocol events.
17
17
  *
@@ -29,25 +29,6 @@ export const parseEventName = (event) => {
29
29
  const [method, ...namespace] = event.split("|");
30
30
  return { method, namespace };
31
31
  };
32
- /**
33
- * Checks whether a namespace starts with a requested prefix.
34
- *
35
- * @param namespace - Event namespace to test.
36
- * @param prefix - Subscription namespace prefix.
37
- * @returns Whether the namespace matches the prefix semantics.
38
- */
39
- export const isPrefixMatch = (namespace, prefix) => {
40
- if (prefix.length > namespace.length)
41
- return false;
42
- return prefix.every((segment, index) => {
43
- const candidate = namespace[index];
44
- if (candidate === segment)
45
- return true;
46
- if (segment.includes(":"))
47
- return false;
48
- return normalizeNamespaceSegment(candidate) === segment;
49
- });
50
- };
51
32
  /**
52
33
  * Guesses a human-readable graph name from a namespace.
53
34
  *
@@ -1,2 +1,2 @@
1
- declare const _default: import("vitest/config.js").UserConfigFnObject;
1
+ declare const _default: import("vitest/config").ViteUserConfigFnObject;
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-api",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "^18.19.0 || >=20.16.0"
@@ -72,11 +72,11 @@
72
72
  "winston": "^3.17.0",
73
73
  "winston-console-format": "^1.0.8",
74
74
  "zod": "^3.25.76 || ^4",
75
- "@langchain/langgraph-ui": "1.2.5"
75
+ "@langchain/langgraph-ui": "1.3.0"
76
76
  },
77
77
  "peerDependencies": {
78
78
  "@langchain/core": "^1.1.48",
79
- "@langchain/langgraph": "^0.2.57 || ^0.3.0 || ^0.4.0 || ^1.0.0-alpha || ^1.0.0 || ^1.3.1-rc.0",
79
+ "@langchain/langgraph": "^1.3.6",
80
80
  "@langchain/langgraph-checkpoint": "~0.0.16 || ^0.1.0 || ~1.0.0",
81
81
  "@langchain/langgraph-sdk": "^1.9.3-rc.0",
82
82
  "typescript": "^5.5.4"
@@ -99,11 +99,11 @@
99
99
  "langchain": "^1.4.4",
100
100
  "postgres": "^3.4.5",
101
101
  "typescript": "^4.9.5 || ^5.4.5",
102
- "vitest": "^3.2.4",
102
+ "vitest": "^4.1.0",
103
103
  "wait-port": "^1.1.0",
104
- "@langchain/langgraph": "1.3.6",
104
+ "@langchain/langgraph": "1.3.7",
105
105
  "@langchain/langgraph-checkpoint": "1.0.4",
106
- "@langchain/langgraph-sdk": "1.9.17"
106
+ "@langchain/langgraph-sdk": "1.9.19"
107
107
  },
108
108
  "scripts": {
109
109
  "clean": "rm -rf dist/ .turbo/ ./tests/graphs/.langgraph_api/ ./tests/protocol-v2/graphs/.langgraph_api/",