@langchain/langgraph-sdk 1.9.16 → 1.9.18

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 (114) hide show
  1. package/dist/client/base.cjs +70 -4
  2. package/dist/client/base.cjs.map +1 -1
  3. package/dist/client/base.d.cts +3 -0
  4. package/dist/client/base.d.cts.map +1 -1
  5. package/dist/client/base.d.ts +3 -0
  6. package/dist/client/base.d.ts.map +1 -1
  7. package/dist/client/base.js +70 -4
  8. package/dist/client/base.js.map +1 -1
  9. package/dist/client/threads/index.cjs +4 -2
  10. package/dist/client/threads/index.cjs.map +1 -1
  11. package/dist/client/threads/index.d.cts.map +1 -1
  12. package/dist/client/threads/index.d.ts.map +1 -1
  13. package/dist/client/threads/index.js +4 -2
  14. package/dist/client/threads/index.js.map +1 -1
  15. package/dist/stream/controller.cjs +451 -32
  16. package/dist/stream/controller.cjs.map +1 -1
  17. package/dist/stream/controller.d.cts +15 -0
  18. package/dist/stream/controller.d.cts.map +1 -1
  19. package/dist/stream/controller.d.ts +15 -0
  20. package/dist/stream/controller.d.ts.map +1 -1
  21. package/dist/stream/controller.js +472 -32
  22. package/dist/stream/controller.js.map +1 -1
  23. package/dist/stream/discovery/index.cjs +2 -0
  24. package/dist/stream/discovery/index.js +3 -0
  25. package/dist/stream/discovery/namespace-from-history.cjs +207 -0
  26. package/dist/stream/discovery/namespace-from-history.cjs.map +1 -0
  27. package/dist/stream/discovery/namespace-from-history.js +204 -0
  28. package/dist/stream/discovery/namespace-from-history.js.map +1 -0
  29. package/dist/stream/discovery/subagents.cjs +56 -1
  30. package/dist/stream/discovery/subagents.cjs.map +1 -1
  31. package/dist/stream/discovery/subagents.d.cts +31 -0
  32. package/dist/stream/discovery/subagents.d.cts.map +1 -1
  33. package/dist/stream/discovery/subagents.d.ts +31 -0
  34. package/dist/stream/discovery/subagents.d.ts.map +1 -1
  35. package/dist/stream/discovery/subagents.js +56 -1
  36. package/dist/stream/discovery/subagents.js.map +1 -1
  37. package/dist/stream/discovery/subgraphs.cjs +24 -0
  38. package/dist/stream/discovery/subgraphs.cjs.map +1 -1
  39. package/dist/stream/discovery/subgraphs.d.cts +13 -0
  40. package/dist/stream/discovery/subgraphs.d.cts.map +1 -1
  41. package/dist/stream/discovery/subgraphs.d.ts +13 -0
  42. package/dist/stream/discovery/subgraphs.d.ts.map +1 -1
  43. package/dist/stream/discovery/subgraphs.js +24 -0
  44. package/dist/stream/discovery/subgraphs.js.map +1 -1
  45. package/dist/stream/index.cjs +1 -0
  46. package/dist/stream/index.js +1 -0
  47. package/dist/stream/message-coercion.cjs +101 -0
  48. package/dist/stream/message-coercion.cjs.map +1 -0
  49. package/dist/stream/message-coercion.d.ts +1 -0
  50. package/dist/stream/message-coercion.js +98 -0
  51. package/dist/stream/message-coercion.js.map +1 -0
  52. package/dist/stream/message-metadata-tracker.cjs +92 -0
  53. package/dist/stream/message-metadata-tracker.cjs.map +1 -1
  54. package/dist/stream/message-metadata-tracker.d.cts +23 -0
  55. package/dist/stream/message-metadata-tracker.d.cts.map +1 -1
  56. package/dist/stream/message-metadata-tracker.d.ts +23 -0
  57. package/dist/stream/message-metadata-tracker.d.ts.map +1 -1
  58. package/dist/stream/message-metadata-tracker.js +92 -0
  59. package/dist/stream/message-metadata-tracker.js.map +1 -1
  60. package/dist/stream/message-reconciliation.cjs +2 -2
  61. package/dist/stream/message-reconciliation.cjs.map +1 -1
  62. package/dist/stream/message-reconciliation.js +2 -2
  63. package/dist/stream/message-reconciliation.js.map +1 -1
  64. package/dist/stream/optimistic-input.cjs +86 -0
  65. package/dist/stream/optimistic-input.cjs.map +1 -0
  66. package/dist/stream/optimistic-input.d.ts +1 -0
  67. package/dist/stream/optimistic-input.js +86 -0
  68. package/dist/stream/optimistic-input.js.map +1 -0
  69. package/dist/stream/projections/channel.cjs +1 -0
  70. package/dist/stream/projections/channel.cjs.map +1 -1
  71. package/dist/stream/projections/channel.d.cts.map +1 -1
  72. package/dist/stream/projections/channel.d.ts.map +1 -1
  73. package/dist/stream/projections/channel.js +1 -0
  74. package/dist/stream/projections/channel.js.map +1 -1
  75. package/dist/stream/projections/messages.cjs +24 -14
  76. package/dist/stream/projections/messages.cjs.map +1 -1
  77. package/dist/stream/projections/messages.js +21 -11
  78. package/dist/stream/projections/messages.js.map +1 -1
  79. package/dist/stream/projections/tool-calls.cjs +22 -10
  80. package/dist/stream/projections/tool-calls.cjs.map +1 -1
  81. package/dist/stream/projections/tool-calls.js +22 -10
  82. package/dist/stream/projections/tool-calls.js.map +1 -1
  83. package/dist/stream/projections/values.cjs +2 -2
  84. package/dist/stream/projections/values.cjs.map +1 -1
  85. package/dist/stream/projections/values.js +1 -1
  86. package/dist/stream/projections/values.js.map +1 -1
  87. package/dist/stream/root-message-projection.cjs +130 -3
  88. package/dist/stream/root-message-projection.cjs.map +1 -1
  89. package/dist/stream/root-message-projection.js +130 -3
  90. package/dist/stream/root-message-projection.js.map +1 -1
  91. package/dist/stream/submit-coordinator.cjs +28 -6
  92. package/dist/stream/submit-coordinator.cjs.map +1 -1
  93. package/dist/stream/submit-coordinator.d.cts.map +1 -1
  94. package/dist/stream/submit-coordinator.d.ts +0 -1
  95. package/dist/stream/submit-coordinator.d.ts.map +1 -1
  96. package/dist/stream/submit-coordinator.js +28 -6
  97. package/dist/stream/submit-coordinator.js.map +1 -1
  98. package/dist/stream/tool-calls.cjs +32 -0
  99. package/dist/stream/tool-calls.cjs.map +1 -1
  100. package/dist/stream/tool-calls.js +32 -1
  101. package/dist/stream/tool-calls.js.map +1 -1
  102. package/dist/stream/types.d.cts +43 -0
  103. package/dist/stream/types.d.cts.map +1 -1
  104. package/dist/stream/types.d.ts +43 -0
  105. package/dist/stream/types.d.ts.map +1 -1
  106. package/dist/ui/index.d.cts +1 -1
  107. package/dist/ui/index.d.ts +1 -1
  108. package/dist/ui/messages.cjs +4 -50
  109. package/dist/ui/messages.cjs.map +1 -1
  110. package/dist/ui/messages.d.cts.map +1 -1
  111. package/dist/ui/messages.d.ts.map +1 -1
  112. package/dist/ui/messages.js +3 -48
  113. package/dist/ui/messages.js.map +1 -1
  114. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"channel.js","names":[],"sources":["../../../src/stream/projections/channel.ts"],"sourcesContent":["/**\n * Raw channel escape hatch.\n *\n * Subscribes to an arbitrary list of channels at an arbitrary\n * namespace and retains a bounded buffer of events. Consumers that\n * need assembly semantics should use `messagesProjection`,\n * `toolCallsProjection`, etc. instead; this one is for inspection,\n * custom reducers, or niche use-cases.\n */\nimport type { Channel, Event } from \"@langchain/protocol\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\n/** Max events retained per raw subscription. Older events are dropped. */\nconst DEFAULT_BUFFER = 4096;\n\nexport interface ChannelProjectionOptions {\n /**\n * Maximum number of events retained in the projection snapshot.\n * Defaults to 4096 so replay-backed discovery hooks can tolerate\n * bursty token/media streams without dropping early lifecycle events.\n */\n bufferSize?: number;\n /**\n * Whether to open a real subscription and receive replayed history.\n * Defaults to true. Set false for live-only root-bus inspection when\n * replay is unnecessary.\n */\n replay?: boolean;\n}\n\nexport function channelProjection(\n channels: readonly Channel[],\n namespace: readonly string[],\n options: ChannelProjectionOptions = {}\n): ProjectionSpec<Event[]> {\n const chs = [...channels].sort();\n const ns = [...namespace];\n const bufferSize = options.bufferSize ?? DEFAULT_BUFFER;\n const replay = options.replay ?? true;\n const key = `channel|${bufferSize}|${replay ? \"replay\" : \"live\"}|${chs.join(\",\")}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n // If this projection is scoped to the root namespace AND every\n // requested channel is already covered by the controller's root\n // pump, attach to the shared fan-out instead of opening a\n // second server subscription. This is the common case for\n // lightweight event-trace / debug panels.\n const covered =\n !replay &&\n isRootNamespace(ns) &&\n chs.every((c) => rootBus.channels.includes(c));\n\n if (covered) {\n const requestedSet = new Set(chs as Channel[]);\n // Pre-compute `custom:<name>` sub-filters so incoming events\n // can be matched in O(1). The server delivers named custom\n // events as `{ method: \"custom\", params: { data: { name } } }`,\n // so matching purely on `event.method` would miss them — we\n // need to peek at `data.name` when the caller asked for a\n // specific `custom:<name>` channel.\n const namedCustom = new Set<string>();\n for (const channel of chs) {\n if (channel.startsWith(\"custom:\")) {\n namedCustom.add(channel.slice(\"custom:\".length));\n }\n }\n const matches = (event: Event): boolean => {\n if (requestedSet.has(event.method as Channel)) return true;\n if (event.method !== \"custom\" || namedCustom.size === 0) {\n return false;\n }\n const data = (event.params as Record<string, unknown>).data as\n | { name?: unknown }\n | undefined;\n return typeof data?.name === \"string\" && namedCustom.has(data.name);\n };\n const push = (event: Event): void => {\n if (!matches(event)) return;\n const current = store.getSnapshot();\n const next =\n current.length >= bufferSize\n ? [...current.slice(current.length - bufferSize + 1), event]\n : [...current, event];\n store.setValue(next);\n };\n const unsubscribe = rootBus.subscribe(push);\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: chs as Channel[],\n namespace: ns,\n onEvent(event) {\n const current = store.getSnapshot();\n const next =\n current.length >= bufferSize\n ? [...current.slice(current.length - bufferSize + 1), event]\n : [...current, event];\n store.setValue(next);\n },\n });\n },\n };\n}\n"],"mappings":";;;;AAeA,MAAM,iBAAiB;AAiBvB,SAAgB,kBACd,UACA,WACA,UAAoC,EAAE,EACb;CACzB,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM;CAChC,MAAM,KAAK,CAAC,GAAG,UAAU;CACzB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,SAAS,QAAQ,UAAU;AAGjC,QAAO;EACL,KAHU,WAAW,WAAW,GAAG,SAAS,WAAW,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,aAAa,GAAG;EAIlG,WAAW;EACX,SAAS,EAAE;EACX,KAAK,EAAE,QAAQ,OAAO,WAA8B;AAWlD,OAJE,CAAC,UACD,gBAAgB,GAAG,IACnB,IAAI,OAAO,MAAM,QAAQ,SAAS,SAAS,EAAE,CAAC,EAEnC;IACX,MAAM,eAAe,IAAI,IAAI,IAAiB;IAO9C,MAAM,8BAAc,IAAI,KAAa;AACrC,SAAK,MAAM,WAAW,IACpB,KAAI,QAAQ,WAAW,UAAU,CAC/B,aAAY,IAAI,QAAQ,MAAM,EAAiB,CAAC;IAGpD,MAAM,WAAW,UAA0B;AACzC,SAAI,aAAa,IAAI,MAAM,OAAkB,CAAE,QAAO;AACtD,SAAI,MAAM,WAAW,YAAY,YAAY,SAAS,EACpD,QAAO;KAET,MAAM,OAAQ,MAAM,OAAmC;AAGvD,YAAO,OAAO,MAAM,SAAS,YAAY,YAAY,IAAI,KAAK,KAAK;;IAErE,MAAM,QAAQ,UAAuB;AACnC,SAAI,CAAC,QAAQ,MAAM,CAAE;KACrB,MAAM,UAAU,MAAM,aAAa;KACnC,MAAM,OACJ,QAAQ,UAAU,aACd,CAAC,GAAG,QAAQ,MAAM,QAAQ,SAAS,aAAa,EAAE,EAAE,MAAM,GAC1D,CAAC,GAAG,SAAS,MAAM;AACzB,WAAM,SAAS,KAAK;;IAEtB,MAAM,cAAc,QAAQ,UAAU,KAAK;AAC3C,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAO,2BAA2B;IAChC;IACA,UAAU;IACV,WAAW;IACX,QAAQ,OAAO;KACb,MAAM,UAAU,MAAM,aAAa;KACnC,MAAM,OACJ,QAAQ,UAAU,aACd,CAAC,GAAG,QAAQ,MAAM,QAAQ,SAAS,aAAa,EAAE,EAAE,MAAM,GAC1D,CAAC,GAAG,SAAS,MAAM;AACzB,WAAM,SAAS,KAAK;;IAEvB,CAAC;;EAEL"}
1
+ {"version":3,"file":"channel.js","names":[],"sources":["../../../src/stream/projections/channel.ts"],"sourcesContent":["/**\n * Raw channel escape hatch.\n *\n * Subscribes to an arbitrary list of channels at an arbitrary\n * namespace and retains a bounded buffer of events. The subscription\n * resumes across serial runs, so the buffer keeps accumulating events\n * for the lifetime of the thread (use `extensionProjection` instead when\n * you only need the most-recent payload of a single `custom:<name>`\n * channel). Consumers that need assembly semantics should use\n * `messagesProjection`, `toolCallsProjection`, etc. instead; this one is\n * for inspection, custom reducers, or niche use-cases.\n */\nimport type { Channel, Event } from \"@langchain/protocol\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\n/** Max events retained per raw subscription. Older events are dropped. */\nconst DEFAULT_BUFFER = 4096;\n\nexport interface ChannelProjectionOptions {\n /**\n * Maximum number of events retained in the projection snapshot.\n * Defaults to 4096 so replay-backed discovery hooks can tolerate\n * bursty token/media streams without dropping early lifecycle events.\n */\n bufferSize?: number;\n /**\n * Whether to open a real subscription and receive replayed history.\n * Defaults to true. Set false for live-only root-bus inspection when\n * replay is unnecessary.\n */\n replay?: boolean;\n}\n\nexport function channelProjection(\n channels: readonly Channel[],\n namespace: readonly string[],\n options: ChannelProjectionOptions = {}\n): ProjectionSpec<Event[]> {\n const chs = [...channels].sort();\n const ns = [...namespace];\n const bufferSize = options.bufferSize ?? DEFAULT_BUFFER;\n const replay = options.replay ?? true;\n const key = `channel|${bufferSize}|${replay ? \"replay\" : \"live\"}|${chs.join(\",\")}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n // If this projection is scoped to the root namespace AND every\n // requested channel is already covered by the controller's root\n // pump, attach to the shared fan-out instead of opening a\n // second server subscription. This is the common case for\n // lightweight event-trace / debug panels.\n const covered =\n !replay &&\n isRootNamespace(ns) &&\n chs.every((c) => rootBus.channels.includes(c));\n\n if (covered) {\n const requestedSet = new Set(chs as Channel[]);\n // Pre-compute `custom:<name>` sub-filters so incoming events\n // can be matched in O(1). The server delivers named custom\n // events as `{ method: \"custom\", params: { data: { name } } }`,\n // so matching purely on `event.method` would miss them — we\n // need to peek at `data.name` when the caller asked for a\n // specific `custom:<name>` channel.\n const namedCustom = new Set<string>();\n for (const channel of chs) {\n if (channel.startsWith(\"custom:\")) {\n namedCustom.add(channel.slice(\"custom:\".length));\n }\n }\n const matches = (event: Event): boolean => {\n if (requestedSet.has(event.method as Channel)) return true;\n if (event.method !== \"custom\" || namedCustom.size === 0) {\n return false;\n }\n const data = (event.params as Record<string, unknown>).data as\n | { name?: unknown }\n | undefined;\n return typeof data?.name === \"string\" && namedCustom.has(data.name);\n };\n const push = (event: Event): void => {\n if (!matches(event)) return;\n const current = store.getSnapshot();\n const next =\n current.length >= bufferSize\n ? [...current.slice(current.length - bufferSize + 1), event]\n : [...current, event];\n store.setValue(next);\n };\n const unsubscribe = rootBus.subscribe(push);\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: chs as Channel[],\n namespace: ns,\n // Keep the buffer alive across serial runs. Some transports pause a\n // subscription on each run's terminal lifecycle event; without\n // resuming, the channel would go silent after the first run and a\n // second prompt on the same thread would emit no further events.\n resumeOnPause: true,\n onEvent(event) {\n const current = store.getSnapshot();\n const next =\n current.length >= bufferSize\n ? [...current.slice(current.length - bufferSize + 1), event]\n : [...current, event];\n store.setValue(next);\n },\n });\n },\n };\n}\n"],"mappings":";;;;AAkBA,MAAM,iBAAiB;AAiBvB,SAAgB,kBACd,UACA,WACA,UAAoC,EAAE,EACb;CACzB,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM;CAChC,MAAM,KAAK,CAAC,GAAG,UAAU;CACzB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,SAAS,QAAQ,UAAU;AAGjC,QAAO;EACL,KAHU,WAAW,WAAW,GAAG,SAAS,WAAW,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,aAAa,GAAG;EAIlG,WAAW;EACX,SAAS,EAAE;EACX,KAAK,EAAE,QAAQ,OAAO,WAA8B;AAWlD,OAJE,CAAC,UACD,gBAAgB,GAAG,IACnB,IAAI,OAAO,MAAM,QAAQ,SAAS,SAAS,EAAE,CAAC,EAEnC;IACX,MAAM,eAAe,IAAI,IAAI,IAAiB;IAO9C,MAAM,8BAAc,IAAI,KAAa;AACrC,SAAK,MAAM,WAAW,IACpB,KAAI,QAAQ,WAAW,UAAU,CAC/B,aAAY,IAAI,QAAQ,MAAM,EAAiB,CAAC;IAGpD,MAAM,WAAW,UAA0B;AACzC,SAAI,aAAa,IAAI,MAAM,OAAkB,CAAE,QAAO;AACtD,SAAI,MAAM,WAAW,YAAY,YAAY,SAAS,EACpD,QAAO;KAET,MAAM,OAAQ,MAAM,OAAmC;AAGvD,YAAO,OAAO,MAAM,SAAS,YAAY,YAAY,IAAI,KAAK,KAAK;;IAErE,MAAM,QAAQ,UAAuB;AACnC,SAAI,CAAC,QAAQ,MAAM,CAAE;KACrB,MAAM,UAAU,MAAM,aAAa;KACnC,MAAM,OACJ,QAAQ,UAAU,aACd,CAAC,GAAG,QAAQ,MAAM,QAAQ,SAAS,aAAa,EAAE,EAAE,MAAM,GAC1D,CAAC,GAAG,SAAS,MAAM;AACzB,WAAM,SAAS,KAAK;;IAEtB,MAAM,cAAc,QAAQ,UAAU,KAAK;AAC3C,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAO,2BAA2B;IAChC;IACA,UAAU;IACV,WAAW;IAKX,eAAe;IACf,QAAQ,OAAO;KACb,MAAM,UAAU,MAAM,aAAa;KACnC,MAAM,OACJ,QAAQ,UAAU,aACd,CAAC,GAAG,QAAQ,MAAM,QAAQ,SAAS,aAAa,EAAE,EAAE,MAAM,GAC1D,CAAC,GAAG,SAAS,MAAM;AACzB,WAAM,SAAS,KAAK;;IAEvB,CAAC;;EAEL"}
@@ -1,5 +1,5 @@
1
- const require_messages = require("../../ui/messages.cjs");
2
- const require_messages$1 = require("../../client/stream/messages.cjs");
1
+ const require_message_coercion = require("../message-coercion.cjs");
2
+ const require_messages = require("../../client/stream/messages.cjs");
3
3
  const require_namespace = require("../namespace.cjs");
4
4
  const require_assembled_to_message = require("../assembled-to-message.cjs");
5
5
  const require_message_reconciliation = require("../message-reconciliation.cjs");
@@ -12,7 +12,7 @@ function messagesProjection(namespace) {
12
12
  namespace: ns,
13
13
  initial: [],
14
14
  open({ thread, store, rootBus }) {
15
- const assembler = new require_messages$1.MessageAssembler();
15
+ const assembler = new require_messages.MessageAssembler();
16
16
  const roleByKey = /* @__PURE__ */ new Map();
17
17
  const indexById = /* @__PURE__ */ new Map();
18
18
  const streamMessageIds = /* @__PURE__ */ new Set();
@@ -52,7 +52,7 @@ function messagesProjection(namespace) {
52
52
  const rawMessages = data.messages;
53
53
  if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;
54
54
  const reconciliation = require_message_reconciliation.reconcileMessagesFromValues({
55
- valueMessages: require_messages.ensureMessageInstances(rawMessages),
55
+ valueMessages: require_message_coercion.ensureMessageInstances(rawMessages),
56
56
  currentMessages: pendingMessages,
57
57
  currentIndexById: indexById,
58
58
  previousValueMessageIds: valuesMessageIds,
@@ -94,15 +94,25 @@ function messagesProjection(namespace) {
94
94
  } else pendingMessages[existingIdx] = base;
95
95
  scheduleFlush();
96
96
  };
97
- const runtime = require_runtime.openProjectionSubscription({
98
- thread,
99
- channels: ["messages", "values"],
100
- namespace: ns,
101
- onEvent(event) {
102
- if (event.method === "messages") applyEvent(event);
103
- else if (event.method === "values") applyValuesEvent(event);
104
- }
105
- });
97
+ let runtime;
98
+ const openSubscription = () => {
99
+ runtime = require_runtime.openProjectionSubscription({
100
+ thread,
101
+ channels: ["messages", "values"],
102
+ namespace: ns,
103
+ onEvent(event) {
104
+ if (event.method === "messages") applyEvent(event);
105
+ else if (event.method === "values") applyValuesEvent(event);
106
+ }
107
+ });
108
+ };
109
+ (async () => {
110
+ if (!(await rootBus.trySeedFromHistory?.({
111
+ kind: "messages",
112
+ namespace: ns,
113
+ store
114
+ }) === true) && !disposed) openSubscription();
115
+ })();
106
116
  return { async dispose() {
107
117
  disposed = true;
108
118
  if (flushChannel != null) {
@@ -110,7 +120,7 @@ function messagesProjection(namespace) {
110
120
  flushChannel.port1.close();
111
121
  flushChannel.port2.close();
112
122
  }
113
- await runtime.dispose();
123
+ await runtime?.dispose();
114
124
  } };
115
125
  }
116
126
  };
@@ -1 +1 @@
1
- {"version":3,"file":"messages.cjs","names":["namespaceKey","MessageAssembler","isRootNamespace","reconcileMessagesFromValues","ensureMessageInstances","shouldPreferValuesMessageForToolCalls","buildMessageIndex","assembledMessageToBaseMessage","openProjectionSubscription"],"sources":["../../../src/stream/projections/messages.ts"],"sourcesContent":["/**\n * Namespace-scoped `messages` projection.\n *\n * Opens `thread.subscribe({ channels: [\"messages\"], namespaces: [ns] })`\n * and folds each `messages` event through {@link MessageAssembler}.\n * Every update — start, block delta, block finish, message finish —\n * re-derives a `BaseMessage` class instance for the currently-active\n * message and updates its slot in the store.\n *\n * The projection emits `BaseMessage[]` (class instances from\n * `@langchain/core/messages`), never plain serialized objects.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"../assembled-to-message.js\";\nimport { ensureMessageInstances } from \"../../ui/messages.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport {\n buildMessageIndex,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"../message-reconciliation.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function messagesProjection(\n namespace: readonly string[]\n): ProjectionSpec<BaseMessage[]> {\n const ns = [...namespace];\n const key = `messages|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new MessageAssembler();\n // Per-messageId state needed for BaseMessage projection:\n // - `role` is only in the `message-start` event; we cache it\n // so subsequent delta events still produce a typed message.\n // - `toolCallId` is pulled from message-start extras when role\n // is `tool` (a convention we keep compatible with serialized\n // v1 tool messages).\n const roleByKey = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n const indexById = new Map<string, number>();\n // Ids this projection has observed via the `messages` channel\n // (token-level deltas). Used by `applyValuesEvent` to prefer the\n // stream-assembled version over the values-coerced shape while a\n // turn is streaming, matching the root controller's policy.\n const streamMessageIds = new Set<string>();\n // Ids observed in the most recent `values.messages` snapshot.\n // Messages that were present in a prior snapshot but are absent\n // from this one are treated as explicit removals (server-side\n // `RemoveMessage` reducer deltas). Stream-only messages (seen on\n // the messages channel but never echoed in a values snapshot)\n // are preserved — their enclosing superstep may simply not have\n // committed yet.\n let valuesMessageIds = new Set<string>();\n\n // Root-scoped projections whose channels are already covered by\n // the controller's root pump attach to the shared fan-out\n // instead of opening a second server subscription. The root\n // pump runs at `{namespaces: [[]], depth: 1}`, which is exactly\n // the scope a root-namespace `messagesProjection` wants.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"messages\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"messages\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyEvent(event as MessagesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n let disposed = false;\n\n // Local mirror of the store contents. Every `applyEvent` /\n // `applyValuesEvent` mutates this synchronously; a coalesced\n // `scheduleFlush` copies it to `store` once per macrotask.\n //\n // Why the indirection? When a namespace-scoped projection\n // (e.g. a subagent modal opened after the run finished) first\n // subscribes, the server replays the entire history from\n // `seq=0`. Dozens of `messages`-channel events can land in a\n // single SSE parse — they drain through the `for await` loop\n // as a long microtask chain. Microtasks run before any\n // macrotask, so React's concurrent scheduler never gets a\n // chance to commit between updates. Calling `store.setValue`\n // per event in that burst overflows React's\n // `nestedUpdateCount` guard and throws \"Maximum update depth\n // exceeded\", permanently killing the projection and\n // leaving the store stuck at its first few messages.\n //\n // Batching via `MessageChannel` (macrotask) coalesces the\n // replay burst into one `setValue` call and lets React\n // commit between flushes for live token streaming too.\n const pendingMessages: BaseMessage[] = [];\n let dirty = false;\n let flushScheduled = false;\n const flushChannel =\n typeof MessageChannel !== \"undefined\" ? new MessageChannel() : null;\n\n const flush = (): void => {\n flushScheduled = false;\n if (!dirty || disposed) return;\n dirty = false;\n // `.slice()` breaks identity so React's `Object.is` bail-out\n // in `StreamStore.setValue` propagates the change.\n store.setValue(pendingMessages.slice());\n };\n if (flushChannel != null) {\n flushChannel.port1.onmessage = flush;\n }\n\n const scheduleFlush = (): void => {\n dirty = true;\n if (flushScheduled) return;\n flushScheduled = true;\n if (flushChannel != null) {\n flushChannel.port2.postMessage(null);\n } else {\n setTimeout(flush, 0);\n }\n };\n\n // Rebuild the store from `values.messages` snapshots.\n //\n // `values` events carry the full, committed state of the\n // thread's `messages` channel at a checkpoint — they fire\n // on node completion, AFTER every `messages`-channel delta\n // for that turn has been emitted. They are the authoritative\n // source of truth for ORDER and for non-streamed messages\n // (human turns, serialised tool results, subagent echoes, …).\n //\n // Why rebuild rather than merge-by-id?\n //\n // In practice the server may emit the same logical message\n // with DIFFERENT ids across successive `values` snapshots at\n // the same namespace — e.g. a subagent first surfaces its\n // seed prompt with a synthetic id like\n // `subagent:<tool_call_id>:human`, then a later superstep\n // echoes the same prompt back with a real UUID (or vice\n // versa). A naive \"match-or-append by id\" strategy treats\n // each fresh id as a new entry and the list grows\n // monotonically, showing the same content twice (or more)\n // in the UI.\n //\n // Policy (mirrors the root controller's `#applyValues`):\n //\n // 1. Walk `values.messages` in order. For each id, prefer\n // the stream-assembled entry if we have one for that id\n // (keeps in-progress token streaming visible); otherwise\n // take the values-coerced instance. This self-heals the\n // two classes of glitch the old merge-by-id handler\n // targeted:\n // - tool messages arriving without `tool_call_id` on\n // the messages channel — the values snapshot always\n // carries it;\n // - AI messages whose finalized `tool_calls` didn't\n // fully land via the messages channel — the values\n // snapshot's AI message has them populated.\n //\n // 2. Append any stream-only ids (seen on the messages\n // channel but never echoed in ANY values snapshot yet)\n // — their enclosing superstep hasn't committed yet, so\n // dropping them would flash the UI.\n //\n // 3. Ids that WERE in a prior values snapshot but are gone\n // from this one are treated as explicit removals\n // (`RemoveMessage` reducer deltas) and dropped.\n //\n // Unkeyed messages (no stable id) are passed through in\n // their values order because we can't dedupe them safely.\n const applyValuesEvent = (event: ValuesEvent): void => {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) {\n return;\n }\n const state = data as Record<string, unknown>;\n const rawMessages = state.messages;\n if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;\n\n const coerced = ensureMessageInstances(\n rawMessages as (Message | BaseMessage)[]\n );\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: coerced,\n currentMessages: pendingMessages,\n currentIndexById: indexById,\n previousValueMessageIds: valuesMessageIds,\n streamedMessageIds: streamMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n });\n valuesMessageIds = reconciliation.valueMessageIds;\n const reconciledMessages = [...reconciliation.messages];\n\n pendingMessages.length = 0;\n for (const message of reconciledMessages) pendingMessages.push(message);\n indexById.clear();\n for (const [id, idx] of buildMessageIndex(pendingMessages)) {\n indexById.set(id, idx);\n }\n scheduleFlush();\n };\n\n const applyEvent = (event: MessagesEvent): void => {\n const data = event.params.data;\n\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n // \"tool\" role is a v1 convention not represented in the\n // protocol enum but common in practice — keep it working\n // for graphs that emit it as an extensible field.\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n const maybeToolCallId = (startData as { tool_call_id?: string })\n .tool_call_id;\n if (startData.id != null) {\n roleByKey.set(startData.id, {\n role: extendedRole,\n toolCallId: maybeToolCallId,\n });\n }\n }\n\n const update = assembler.consume(event);\n if (update == null) return;\n const msg = update.message;\n const id = msg.id;\n if (id == null) return;\n const captured = roleByKey.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(msg, captured.role, {\n toolCallId: captured.toolCallId,\n });\n\n streamMessageIds.add(id);\n const existingIdx = indexById.get(id);\n if (existingIdx == null) {\n indexById.set(id, pendingMessages.length);\n pendingMessages.push(base);\n } else {\n pendingMessages[existingIdx] = base;\n }\n scheduleFlush();\n };\n\n const runtime = openProjectionSubscription({\n thread,\n // Subscribe to both `messages` (live token deltas that drive\n // the in-flight assistant bubble) and `values` (periodic full-\n // state snapshots). Consuming values lets late-mounted scoped\n // projections backfill history after the run has finished.\n channels: [\"messages\", \"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method === \"messages\") {\n applyEvent(event as MessagesEvent);\n } else if (event.method === \"values\") {\n applyValuesEvent(event as ValuesEvent);\n }\n },\n });\n\n return {\n async dispose() {\n disposed = true;\n if (flushChannel != null) {\n flushChannel.port1.onmessage = null;\n flushChannel.port1.close();\n flushChannel.port2.close();\n }\n await runtime.dispose();\n },\n };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"messages.cjs","names":["namespaceKey","MessageAssembler","isRootNamespace","reconcileMessagesFromValues","ensureMessageInstances","shouldPreferValuesMessageForToolCalls","buildMessageIndex","assembledMessageToBaseMessage","openProjectionSubscription"],"sources":["../../../src/stream/projections/messages.ts"],"sourcesContent":["/**\n * Namespace-scoped `messages` projection.\n *\n * Opens `thread.subscribe({ channels: [\"messages\"], namespaces: [ns] })`\n * and folds each `messages` event through {@link MessageAssembler}.\n * Every update — start, block delta, block finish, message finish —\n * re-derives a `BaseMessage` class instance for the currently-active\n * message and updates its slot in the store.\n *\n * The projection emits `BaseMessage[]` (class instances from\n * `@langchain/core/messages`), never plain serialized objects.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"../assembled-to-message.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { ensureMessageInstances } from \"../message-coercion.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport {\n buildMessageIndex,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"../message-reconciliation.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function messagesProjection(\n namespace: readonly string[]\n): ProjectionSpec<BaseMessage[]> {\n const ns = [...namespace];\n const key = `messages|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new MessageAssembler();\n // Per-messageId state needed for BaseMessage projection:\n // - `role` is only in the `message-start` event; we cache it\n // so subsequent delta events still produce a typed message.\n // - `toolCallId` is pulled from message-start extras when role\n // is `tool` (a convention we keep compatible with serialized\n // v1 tool messages).\n const roleByKey = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n const indexById = new Map<string, number>();\n // Ids this projection has observed via the `messages` channel\n // (token-level deltas). Used by `applyValuesEvent` to prefer the\n // stream-assembled version over the values-coerced shape while a\n // turn is streaming, matching the root controller's policy.\n const streamMessageIds = new Set<string>();\n // Ids observed in the most recent `values.messages` snapshot.\n // Messages that were present in a prior snapshot but are absent\n // from this one are treated as explicit removals (server-side\n // `RemoveMessage` reducer deltas). Stream-only messages (seen on\n // the messages channel but never echoed in a values snapshot)\n // are preserved — their enclosing superstep may simply not have\n // committed yet.\n let valuesMessageIds = new Set<string>();\n\n // Root-scoped projections whose channels are already covered by\n // the controller's root pump attach to the shared fan-out\n // instead of opening a second server subscription. The root\n // pump runs at `{namespaces: [[]], depth: 1}`, which is exactly\n // the scope a root-namespace `messagesProjection` wants.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"messages\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"messages\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyEvent(event as MessagesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n let disposed = false;\n\n // Local mirror of the store contents. Every `applyEvent` /\n // `applyValuesEvent` mutates this synchronously; a coalesced\n // `scheduleFlush` copies it to `store` once per macrotask.\n //\n // Why the indirection? When a namespace-scoped projection\n // (e.g. a subagent modal opened after the run finished) first\n // subscribes, the server replays the entire history from\n // `seq=0`. Dozens of `messages`-channel events can land in a\n // single SSE parse — they drain through the `for await` loop\n // as a long microtask chain. Microtasks run before any\n // macrotask, so React's concurrent scheduler never gets a\n // chance to commit between updates. Calling `store.setValue`\n // per event in that burst overflows React's\n // `nestedUpdateCount` guard and throws \"Maximum update depth\n // exceeded\", permanently killing the projection and\n // leaving the store stuck at its first few messages.\n //\n // Batching via `MessageChannel` (macrotask) coalesces the\n // replay burst into one `setValue` call and lets React\n // commit between flushes for live token streaming too.\n const pendingMessages: BaseMessage[] = [];\n let dirty = false;\n let flushScheduled = false;\n const flushChannel =\n typeof MessageChannel !== \"undefined\" ? new MessageChannel() : null;\n\n const flush = (): void => {\n flushScheduled = false;\n if (!dirty || disposed) return;\n dirty = false;\n // `.slice()` breaks identity so React's `Object.is` bail-out\n // in `StreamStore.setValue` propagates the change.\n store.setValue(pendingMessages.slice());\n };\n if (flushChannel != null) {\n flushChannel.port1.onmessage = flush;\n }\n\n const scheduleFlush = (): void => {\n dirty = true;\n if (flushScheduled) return;\n flushScheduled = true;\n if (flushChannel != null) {\n flushChannel.port2.postMessage(null);\n } else {\n setTimeout(flush, 0);\n }\n };\n\n // Rebuild the store from `values.messages` snapshots.\n //\n // `values` events carry the full, committed state of the\n // thread's `messages` channel at a checkpoint — they fire\n // on node completion, AFTER every `messages`-channel delta\n // for that turn has been emitted. They are the authoritative\n // source of truth for ORDER and for non-streamed messages\n // (human turns, serialised tool results, subagent echoes, …).\n //\n // Why rebuild rather than merge-by-id?\n //\n // In practice the server may emit the same logical message\n // with DIFFERENT ids across successive `values` snapshots at\n // the same namespace — e.g. a subagent first surfaces its\n // seed prompt with a synthetic id like\n // `subagent:<tool_call_id>:human`, then a later superstep\n // echoes the same prompt back with a real UUID (or vice\n // versa). A naive \"match-or-append by id\" strategy treats\n // each fresh id as a new entry and the list grows\n // monotonically, showing the same content twice (or more)\n // in the UI.\n //\n // Policy (mirrors the root controller's `#applyValues`):\n //\n // 1. Walk `values.messages` in order. For each id, prefer\n // the stream-assembled entry if we have one for that id\n // (keeps in-progress token streaming visible); otherwise\n // take the values-coerced instance. This self-heals the\n // two classes of glitch the old merge-by-id handler\n // targeted:\n // - tool messages arriving without `tool_call_id` on\n // the messages channel — the values snapshot always\n // carries it;\n // - AI messages whose finalized `tool_calls` didn't\n // fully land via the messages channel — the values\n // snapshot's AI message has them populated.\n //\n // 2. Append any stream-only ids (seen on the messages\n // channel but never echoed in ANY values snapshot yet)\n // — their enclosing superstep hasn't committed yet, so\n // dropping them would flash the UI.\n //\n // 3. Ids that WERE in a prior values snapshot but are gone\n // from this one are treated as explicit removals\n // (`RemoveMessage` reducer deltas) and dropped.\n //\n // Unkeyed messages (no stable id) are passed through in\n // their values order because we can't dedupe them safely.\n const applyValuesEvent = (event: ValuesEvent): void => {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) {\n return;\n }\n const state = data as Record<string, unknown>;\n const rawMessages = state.messages;\n if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;\n\n const coerced = ensureMessageInstances(\n rawMessages as (Message | BaseMessage)[]\n );\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: coerced,\n currentMessages: pendingMessages,\n currentIndexById: indexById,\n previousValueMessageIds: valuesMessageIds,\n streamedMessageIds: streamMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n });\n valuesMessageIds = reconciliation.valueMessageIds;\n const reconciledMessages = [...reconciliation.messages];\n\n pendingMessages.length = 0;\n for (const message of reconciledMessages) pendingMessages.push(message);\n indexById.clear();\n for (const [id, idx] of buildMessageIndex(pendingMessages)) {\n indexById.set(id, idx);\n }\n scheduleFlush();\n };\n\n const applyEvent = (event: MessagesEvent): void => {\n const data = event.params.data;\n\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n // \"tool\" role is a v1 convention not represented in the\n // protocol enum but common in practice — keep it working\n // for graphs that emit it as an extensible field.\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n const maybeToolCallId = (startData as { tool_call_id?: string })\n .tool_call_id;\n if (startData.id != null) {\n roleByKey.set(startData.id, {\n role: extendedRole,\n toolCallId: maybeToolCallId,\n });\n }\n }\n\n const update = assembler.consume(event);\n if (update == null) return;\n const msg = update.message;\n const id = msg.id;\n if (id == null) return;\n const captured = roleByKey.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(msg, captured.role, {\n toolCallId: captured.toolCallId,\n });\n\n streamMessageIds.add(id);\n const existingIdx = indexById.get(id);\n if (existingIdx == null) {\n indexById.set(id, pendingMessages.length);\n pendingMessages.push(base);\n } else {\n pendingMessages[existingIdx] = base;\n }\n scheduleFlush();\n };\n\n let runtime: ProjectionRuntime | undefined;\n const openSubscription = () => {\n runtime = openProjectionSubscription({\n thread,\n // Subscribe to both `messages` (live token deltas that drive\n // the in-flight assistant bubble) and `values` (periodic full-\n // state snapshots). Consuming values lets late-mounted scoped\n // projections backfill history after the run has finished.\n channels: [\"messages\", \"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method === \"messages\") {\n applyEvent(event as MessagesEvent);\n } else if (event.method === \"values\") {\n applyValuesEvent(event as ValuesEvent);\n }\n },\n });\n };\n\n void (async () => {\n const seeded =\n (await rootBus.trySeedFromHistory?.({\n kind: \"messages\",\n namespace: ns,\n store,\n })) === true;\n if (!seeded && !disposed) openSubscription();\n })();\n\n return {\n async dispose() {\n disposed = true;\n if (flushChannel != null) {\n flushChannel.port1.onmessage = null;\n flushChannel.port1.close();\n flushChannel.port2.close();\n }\n await runtime?.dispose();\n },\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ensureMessageInstances } from "../../ui/messages.js";
1
+ import { ensureMessageInstances } from "../message-coercion.js";
2
2
  import { MessageAssembler } from "../../client/stream/messages.js";
3
3
  import { isRootNamespace, namespaceKey } from "../namespace.js";
4
4
  import { assembledMessageToBaseMessage } from "../assembled-to-message.js";
@@ -94,15 +94,25 @@ function messagesProjection(namespace) {
94
94
  } else pendingMessages[existingIdx] = base;
95
95
  scheduleFlush();
96
96
  };
97
- const runtime = openProjectionSubscription({
98
- thread,
99
- channels: ["messages", "values"],
100
- namespace: ns,
101
- onEvent(event) {
102
- if (event.method === "messages") applyEvent(event);
103
- else if (event.method === "values") applyValuesEvent(event);
104
- }
105
- });
97
+ let runtime;
98
+ const openSubscription = () => {
99
+ runtime = openProjectionSubscription({
100
+ thread,
101
+ channels: ["messages", "values"],
102
+ namespace: ns,
103
+ onEvent(event) {
104
+ if (event.method === "messages") applyEvent(event);
105
+ else if (event.method === "values") applyValuesEvent(event);
106
+ }
107
+ });
108
+ };
109
+ (async () => {
110
+ if (!(await rootBus.trySeedFromHistory?.({
111
+ kind: "messages",
112
+ namespace: ns,
113
+ store
114
+ }) === true) && !disposed) openSubscription();
115
+ })();
106
116
  return { async dispose() {
107
117
  disposed = true;
108
118
  if (flushChannel != null) {
@@ -110,7 +120,7 @@ function messagesProjection(namespace) {
110
120
  flushChannel.port1.close();
111
121
  flushChannel.port2.close();
112
122
  }
113
- await runtime.dispose();
123
+ await runtime?.dispose();
114
124
  } };
115
125
  }
116
126
  };
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","names":[],"sources":["../../../src/stream/projections/messages.ts"],"sourcesContent":["/**\n * Namespace-scoped `messages` projection.\n *\n * Opens `thread.subscribe({ channels: [\"messages\"], namespaces: [ns] })`\n * and folds each `messages` event through {@link MessageAssembler}.\n * Every update — start, block delta, block finish, message finish —\n * re-derives a `BaseMessage` class instance for the currently-active\n * message and updates its slot in the store.\n *\n * The projection emits `BaseMessage[]` (class instances from\n * `@langchain/core/messages`), never plain serialized objects.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"../assembled-to-message.js\";\nimport { ensureMessageInstances } from \"../../ui/messages.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport {\n buildMessageIndex,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"../message-reconciliation.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function messagesProjection(\n namespace: readonly string[]\n): ProjectionSpec<BaseMessage[]> {\n const ns = [...namespace];\n const key = `messages|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new MessageAssembler();\n // Per-messageId state needed for BaseMessage projection:\n // - `role` is only in the `message-start` event; we cache it\n // so subsequent delta events still produce a typed message.\n // - `toolCallId` is pulled from message-start extras when role\n // is `tool` (a convention we keep compatible with serialized\n // v1 tool messages).\n const roleByKey = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n const indexById = new Map<string, number>();\n // Ids this projection has observed via the `messages` channel\n // (token-level deltas). Used by `applyValuesEvent` to prefer the\n // stream-assembled version over the values-coerced shape while a\n // turn is streaming, matching the root controller's policy.\n const streamMessageIds = new Set<string>();\n // Ids observed in the most recent `values.messages` snapshot.\n // Messages that were present in a prior snapshot but are absent\n // from this one are treated as explicit removals (server-side\n // `RemoveMessage` reducer deltas). Stream-only messages (seen on\n // the messages channel but never echoed in a values snapshot)\n // are preserved — their enclosing superstep may simply not have\n // committed yet.\n let valuesMessageIds = new Set<string>();\n\n // Root-scoped projections whose channels are already covered by\n // the controller's root pump attach to the shared fan-out\n // instead of opening a second server subscription. The root\n // pump runs at `{namespaces: [[]], depth: 1}`, which is exactly\n // the scope a root-namespace `messagesProjection` wants.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"messages\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"messages\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyEvent(event as MessagesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n let disposed = false;\n\n // Local mirror of the store contents. Every `applyEvent` /\n // `applyValuesEvent` mutates this synchronously; a coalesced\n // `scheduleFlush` copies it to `store` once per macrotask.\n //\n // Why the indirection? When a namespace-scoped projection\n // (e.g. a subagent modal opened after the run finished) first\n // subscribes, the server replays the entire history from\n // `seq=0`. Dozens of `messages`-channel events can land in a\n // single SSE parse — they drain through the `for await` loop\n // as a long microtask chain. Microtasks run before any\n // macrotask, so React's concurrent scheduler never gets a\n // chance to commit between updates. Calling `store.setValue`\n // per event in that burst overflows React's\n // `nestedUpdateCount` guard and throws \"Maximum update depth\n // exceeded\", permanently killing the projection and\n // leaving the store stuck at its first few messages.\n //\n // Batching via `MessageChannel` (macrotask) coalesces the\n // replay burst into one `setValue` call and lets React\n // commit between flushes for live token streaming too.\n const pendingMessages: BaseMessage[] = [];\n let dirty = false;\n let flushScheduled = false;\n const flushChannel =\n typeof MessageChannel !== \"undefined\" ? new MessageChannel() : null;\n\n const flush = (): void => {\n flushScheduled = false;\n if (!dirty || disposed) return;\n dirty = false;\n // `.slice()` breaks identity so React's `Object.is` bail-out\n // in `StreamStore.setValue` propagates the change.\n store.setValue(pendingMessages.slice());\n };\n if (flushChannel != null) {\n flushChannel.port1.onmessage = flush;\n }\n\n const scheduleFlush = (): void => {\n dirty = true;\n if (flushScheduled) return;\n flushScheduled = true;\n if (flushChannel != null) {\n flushChannel.port2.postMessage(null);\n } else {\n setTimeout(flush, 0);\n }\n };\n\n // Rebuild the store from `values.messages` snapshots.\n //\n // `values` events carry the full, committed state of the\n // thread's `messages` channel at a checkpoint — they fire\n // on node completion, AFTER every `messages`-channel delta\n // for that turn has been emitted. They are the authoritative\n // source of truth for ORDER and for non-streamed messages\n // (human turns, serialised tool results, subagent echoes, …).\n //\n // Why rebuild rather than merge-by-id?\n //\n // In practice the server may emit the same logical message\n // with DIFFERENT ids across successive `values` snapshots at\n // the same namespace — e.g. a subagent first surfaces its\n // seed prompt with a synthetic id like\n // `subagent:<tool_call_id>:human`, then a later superstep\n // echoes the same prompt back with a real UUID (or vice\n // versa). A naive \"match-or-append by id\" strategy treats\n // each fresh id as a new entry and the list grows\n // monotonically, showing the same content twice (or more)\n // in the UI.\n //\n // Policy (mirrors the root controller's `#applyValues`):\n //\n // 1. Walk `values.messages` in order. For each id, prefer\n // the stream-assembled entry if we have one for that id\n // (keeps in-progress token streaming visible); otherwise\n // take the values-coerced instance. This self-heals the\n // two classes of glitch the old merge-by-id handler\n // targeted:\n // - tool messages arriving without `tool_call_id` on\n // the messages channel — the values snapshot always\n // carries it;\n // - AI messages whose finalized `tool_calls` didn't\n // fully land via the messages channel — the values\n // snapshot's AI message has them populated.\n //\n // 2. Append any stream-only ids (seen on the messages\n // channel but never echoed in ANY values snapshot yet)\n // — their enclosing superstep hasn't committed yet, so\n // dropping them would flash the UI.\n //\n // 3. Ids that WERE in a prior values snapshot but are gone\n // from this one are treated as explicit removals\n // (`RemoveMessage` reducer deltas) and dropped.\n //\n // Unkeyed messages (no stable id) are passed through in\n // their values order because we can't dedupe them safely.\n const applyValuesEvent = (event: ValuesEvent): void => {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) {\n return;\n }\n const state = data as Record<string, unknown>;\n const rawMessages = state.messages;\n if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;\n\n const coerced = ensureMessageInstances(\n rawMessages as (Message | BaseMessage)[]\n );\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: coerced,\n currentMessages: pendingMessages,\n currentIndexById: indexById,\n previousValueMessageIds: valuesMessageIds,\n streamedMessageIds: streamMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n });\n valuesMessageIds = reconciliation.valueMessageIds;\n const reconciledMessages = [...reconciliation.messages];\n\n pendingMessages.length = 0;\n for (const message of reconciledMessages) pendingMessages.push(message);\n indexById.clear();\n for (const [id, idx] of buildMessageIndex(pendingMessages)) {\n indexById.set(id, idx);\n }\n scheduleFlush();\n };\n\n const applyEvent = (event: MessagesEvent): void => {\n const data = event.params.data;\n\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n // \"tool\" role is a v1 convention not represented in the\n // protocol enum but common in practice — keep it working\n // for graphs that emit it as an extensible field.\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n const maybeToolCallId = (startData as { tool_call_id?: string })\n .tool_call_id;\n if (startData.id != null) {\n roleByKey.set(startData.id, {\n role: extendedRole,\n toolCallId: maybeToolCallId,\n });\n }\n }\n\n const update = assembler.consume(event);\n if (update == null) return;\n const msg = update.message;\n const id = msg.id;\n if (id == null) return;\n const captured = roleByKey.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(msg, captured.role, {\n toolCallId: captured.toolCallId,\n });\n\n streamMessageIds.add(id);\n const existingIdx = indexById.get(id);\n if (existingIdx == null) {\n indexById.set(id, pendingMessages.length);\n pendingMessages.push(base);\n } else {\n pendingMessages[existingIdx] = base;\n }\n scheduleFlush();\n };\n\n const runtime = openProjectionSubscription({\n thread,\n // Subscribe to both `messages` (live token deltas that drive\n // the in-flight assistant bubble) and `values` (periodic full-\n // state snapshots). Consuming values lets late-mounted scoped\n // projections backfill history after the run has finished.\n channels: [\"messages\", \"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method === \"messages\") {\n applyEvent(event as MessagesEvent);\n } else if (event.method === \"values\") {\n applyValuesEvent(event as ValuesEvent);\n }\n },\n });\n\n return {\n async dispose() {\n disposed = true;\n if (flushChannel != null) {\n flushChannel.port1.onmessage = null;\n flushChannel.port1.close();\n flushChannel.port2.close();\n }\n await runtime.dispose();\n },\n };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"messages.js","names":[],"sources":["../../../src/stream/projections/messages.ts"],"sourcesContent":["/**\n * Namespace-scoped `messages` projection.\n *\n * Opens `thread.subscribe({ channels: [\"messages\"], namespaces: [ns] })`\n * and folds each `messages` event through {@link MessageAssembler}.\n * Every update — start, block delta, block finish, message finish —\n * re-derives a `BaseMessage` class instance for the currently-active\n * message and updates its slot in the store.\n *\n * The projection emits `BaseMessage[]` (class instances from\n * `@langchain/core/messages`), never plain serialized objects.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"../assembled-to-message.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { ensureMessageInstances } from \"../message-coercion.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport {\n buildMessageIndex,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"../message-reconciliation.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function messagesProjection(\n namespace: readonly string[]\n): ProjectionSpec<BaseMessage[]> {\n const ns = [...namespace];\n const key = `messages|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new MessageAssembler();\n // Per-messageId state needed for BaseMessage projection:\n // - `role` is only in the `message-start` event; we cache it\n // so subsequent delta events still produce a typed message.\n // - `toolCallId` is pulled from message-start extras when role\n // is `tool` (a convention we keep compatible with serialized\n // v1 tool messages).\n const roleByKey = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n const indexById = new Map<string, number>();\n // Ids this projection has observed via the `messages` channel\n // (token-level deltas). Used by `applyValuesEvent` to prefer the\n // stream-assembled version over the values-coerced shape while a\n // turn is streaming, matching the root controller's policy.\n const streamMessageIds = new Set<string>();\n // Ids observed in the most recent `values.messages` snapshot.\n // Messages that were present in a prior snapshot but are absent\n // from this one are treated as explicit removals (server-side\n // `RemoveMessage` reducer deltas). Stream-only messages (seen on\n // the messages channel but never echoed in a values snapshot)\n // are preserved — their enclosing superstep may simply not have\n // committed yet.\n let valuesMessageIds = new Set<string>();\n\n // Root-scoped projections whose channels are already covered by\n // the controller's root pump attach to the shared fan-out\n // instead of opening a second server subscription. The root\n // pump runs at `{namespaces: [[]], depth: 1}`, which is exactly\n // the scope a root-namespace `messagesProjection` wants.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"messages\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"messages\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyEvent(event as MessagesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n let disposed = false;\n\n // Local mirror of the store contents. Every `applyEvent` /\n // `applyValuesEvent` mutates this synchronously; a coalesced\n // `scheduleFlush` copies it to `store` once per macrotask.\n //\n // Why the indirection? When a namespace-scoped projection\n // (e.g. a subagent modal opened after the run finished) first\n // subscribes, the server replays the entire history from\n // `seq=0`. Dozens of `messages`-channel events can land in a\n // single SSE parse — they drain through the `for await` loop\n // as a long microtask chain. Microtasks run before any\n // macrotask, so React's concurrent scheduler never gets a\n // chance to commit between updates. Calling `store.setValue`\n // per event in that burst overflows React's\n // `nestedUpdateCount` guard and throws \"Maximum update depth\n // exceeded\", permanently killing the projection and\n // leaving the store stuck at its first few messages.\n //\n // Batching via `MessageChannel` (macrotask) coalesces the\n // replay burst into one `setValue` call and lets React\n // commit between flushes for live token streaming too.\n const pendingMessages: BaseMessage[] = [];\n let dirty = false;\n let flushScheduled = false;\n const flushChannel =\n typeof MessageChannel !== \"undefined\" ? new MessageChannel() : null;\n\n const flush = (): void => {\n flushScheduled = false;\n if (!dirty || disposed) return;\n dirty = false;\n // `.slice()` breaks identity so React's `Object.is` bail-out\n // in `StreamStore.setValue` propagates the change.\n store.setValue(pendingMessages.slice());\n };\n if (flushChannel != null) {\n flushChannel.port1.onmessage = flush;\n }\n\n const scheduleFlush = (): void => {\n dirty = true;\n if (flushScheduled) return;\n flushScheduled = true;\n if (flushChannel != null) {\n flushChannel.port2.postMessage(null);\n } else {\n setTimeout(flush, 0);\n }\n };\n\n // Rebuild the store from `values.messages` snapshots.\n //\n // `values` events carry the full, committed state of the\n // thread's `messages` channel at a checkpoint — they fire\n // on node completion, AFTER every `messages`-channel delta\n // for that turn has been emitted. They are the authoritative\n // source of truth for ORDER and for non-streamed messages\n // (human turns, serialised tool results, subagent echoes, …).\n //\n // Why rebuild rather than merge-by-id?\n //\n // In practice the server may emit the same logical message\n // with DIFFERENT ids across successive `values` snapshots at\n // the same namespace — e.g. a subagent first surfaces its\n // seed prompt with a synthetic id like\n // `subagent:<tool_call_id>:human`, then a later superstep\n // echoes the same prompt back with a real UUID (or vice\n // versa). A naive \"match-or-append by id\" strategy treats\n // each fresh id as a new entry and the list grows\n // monotonically, showing the same content twice (or more)\n // in the UI.\n //\n // Policy (mirrors the root controller's `#applyValues`):\n //\n // 1. Walk `values.messages` in order. For each id, prefer\n // the stream-assembled entry if we have one for that id\n // (keeps in-progress token streaming visible); otherwise\n // take the values-coerced instance. This self-heals the\n // two classes of glitch the old merge-by-id handler\n // targeted:\n // - tool messages arriving without `tool_call_id` on\n // the messages channel — the values snapshot always\n // carries it;\n // - AI messages whose finalized `tool_calls` didn't\n // fully land via the messages channel — the values\n // snapshot's AI message has them populated.\n //\n // 2. Append any stream-only ids (seen on the messages\n // channel but never echoed in ANY values snapshot yet)\n // — their enclosing superstep hasn't committed yet, so\n // dropping them would flash the UI.\n //\n // 3. Ids that WERE in a prior values snapshot but are gone\n // from this one are treated as explicit removals\n // (`RemoveMessage` reducer deltas) and dropped.\n //\n // Unkeyed messages (no stable id) are passed through in\n // their values order because we can't dedupe them safely.\n const applyValuesEvent = (event: ValuesEvent): void => {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) {\n return;\n }\n const state = data as Record<string, unknown>;\n const rawMessages = state.messages;\n if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;\n\n const coerced = ensureMessageInstances(\n rawMessages as (Message | BaseMessage)[]\n );\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: coerced,\n currentMessages: pendingMessages,\n currentIndexById: indexById,\n previousValueMessageIds: valuesMessageIds,\n streamedMessageIds: streamMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n });\n valuesMessageIds = reconciliation.valueMessageIds;\n const reconciledMessages = [...reconciliation.messages];\n\n pendingMessages.length = 0;\n for (const message of reconciledMessages) pendingMessages.push(message);\n indexById.clear();\n for (const [id, idx] of buildMessageIndex(pendingMessages)) {\n indexById.set(id, idx);\n }\n scheduleFlush();\n };\n\n const applyEvent = (event: MessagesEvent): void => {\n const data = event.params.data;\n\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n // \"tool\" role is a v1 convention not represented in the\n // protocol enum but common in practice — keep it working\n // for graphs that emit it as an extensible field.\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n const maybeToolCallId = (startData as { tool_call_id?: string })\n .tool_call_id;\n if (startData.id != null) {\n roleByKey.set(startData.id, {\n role: extendedRole,\n toolCallId: maybeToolCallId,\n });\n }\n }\n\n const update = assembler.consume(event);\n if (update == null) return;\n const msg = update.message;\n const id = msg.id;\n if (id == null) return;\n const captured = roleByKey.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(msg, captured.role, {\n toolCallId: captured.toolCallId,\n });\n\n streamMessageIds.add(id);\n const existingIdx = indexById.get(id);\n if (existingIdx == null) {\n indexById.set(id, pendingMessages.length);\n pendingMessages.push(base);\n } else {\n pendingMessages[existingIdx] = base;\n }\n scheduleFlush();\n };\n\n let runtime: ProjectionRuntime | undefined;\n const openSubscription = () => {\n runtime = openProjectionSubscription({\n thread,\n // Subscribe to both `messages` (live token deltas that drive\n // the in-flight assistant bubble) and `values` (periodic full-\n // state snapshots). Consuming values lets late-mounted scoped\n // projections backfill history after the run has finished.\n channels: [\"messages\", \"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method === \"messages\") {\n applyEvent(event as MessagesEvent);\n } else if (event.method === \"values\") {\n applyValuesEvent(event as ValuesEvent);\n }\n },\n });\n };\n\n void (async () => {\n const seeded =\n (await rootBus.trySeedFromHistory?.({\n kind: \"messages\",\n namespace: ns,\n store,\n })) === true;\n if (!seeded && !disposed) openSubscription();\n })();\n\n return {\n async dispose() {\n disposed = true;\n if (flushChannel != null) {\n flushChannel.port1.onmessage = null;\n flushChannel.port1.close();\n flushChannel.port2.close();\n }\n await runtime?.dispose();\n },\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -28,17 +28,29 @@ function toolCallsProjection(namespace) {
28
28
  unsubscribe();
29
29
  } };
30
30
  }
31
- const runtime = require_runtime.openProjectionSubscription({
32
- thread,
33
- channels: ["tools"],
34
- namespace: ns,
35
- onEvent(event) {
36
- if (event.method !== "tools") return;
37
- applyToolsEvent(event);
38
- }
39
- });
31
+ let runtime;
32
+ const openSubscription = () => {
33
+ runtime = require_runtime.openProjectionSubscription({
34
+ thread,
35
+ channels: ["tools"],
36
+ namespace: ns,
37
+ onEvent(event) {
38
+ if (event.method !== "tools") return;
39
+ applyToolsEvent(event);
40
+ }
41
+ });
42
+ };
43
+ let disposed = false;
44
+ (async () => {
45
+ if (!(await rootBus.trySeedFromHistory?.({
46
+ kind: "toolCalls",
47
+ namespace: ns,
48
+ store
49
+ }) === true) && !disposed) openSubscription();
50
+ })();
40
51
  return { async dispose() {
41
- await runtime.dispose();
52
+ disposed = true;
53
+ await runtime?.dispose();
42
54
  } };
43
55
  }
44
56
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tool-calls.cjs","names":["namespaceKey","ToolCallAssembler","shouldIgnoreScopedTaskToolEvent","upsertToolCall","isRootNamespace","openProjectionSubscription"],"sources":["../../../src/stream/projections/tool-calls.ts"],"sourcesContent":["/**\n * Namespace-scoped `tools` projection.\n *\n * Opens `thread.subscribe({ channels: [\"tools\"], namespaces: [ns] })`,\n * feeds events through {@link ToolCallAssembler}, and surfaces an\n * array of {@link AssembledToolCall}s that grows as calls are\n * discovered. Each handle exposes reactive {@link AssembledToolCall.status},\n * {@link AssembledToolCall.error}, and {@link AssembledToolCall.output}\n * (`null` until the call succeeds).\n */\nimport type { ToolsEvent } from \"@langchain/protocol\";\nimport {\n shouldIgnoreScopedTaskToolEvent,\n ToolCallAssembler,\n} from \"../../client/stream/handles/tools.js\";\nimport type { AssembledToolCall } from \"../../client/stream/handles/tools.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { upsertToolCall } from \"../tool-calls.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function toolCallsProjection(\n namespace: readonly string[]\n): ProjectionSpec<AssembledToolCall[]> {\n const ns = [...namespace];\n const key = `toolCalls|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new ToolCallAssembler();\n\n const applyToolsEvent = (event: ToolsEvent): void => {\n if (shouldIgnoreScopedTaskToolEvent(ns, event)) return;\n const tc = assembler.consume(event);\n if (tc == null) return;\n const next = upsertToolCall(store.getSnapshot(), tc);\n store.setValue(next);\n };\n\n // See `messagesProjection` — root-scoped projections short-\n // circuit onto the root bus when the requested channels are\n // covered by the controller's root pump.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"tools\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"tools\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyToolsEvent(event as ToolsEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n const runtime = openProjectionSubscription({\n thread,\n channels: [\"tools\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"tools\") return;\n applyToolsEvent(event as ToolsEvent);\n },\n });\n\n return {\n async dispose() {\n await runtime.dispose();\n },\n };\n },\n };\n}\n"],"mappings":";;;;;AAqBA,SAAgB,oBACd,WACqC;CACrC,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,aAAaA,kBAAAA,aAAa,GAAG;EAIvC,WAAW;EACX,SAAS,EAAE;EACX,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,YAAY,IAAIC,cAAAA,mBAAmB;GAEzC,MAAM,mBAAmB,UAA4B;AACnD,QAAIC,cAAAA,gCAAgC,IAAI,MAAM,CAAE;IAChD,MAAM,KAAK,UAAU,QAAQ,MAAM;AACnC,QAAI,MAAM,KAAM;IAChB,MAAM,OAAOC,mBAAAA,eAAe,MAAM,aAAa,EAAE,GAAG;AACpD,UAAM,SAAS,KAAK;;AAStB,OAFEC,kBAAAA,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,QAAQ,EAErC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,QAAS;AAC9B,SAAI,CAACA,kBAAAA,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,qBAAgB,MAAoB;MACpC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;GAGH,MAAM,UAAUC,gBAAAA,2BAA2B;IACzC;IACA,UAAU,CAAC,QAAQ;IACnB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,QAAS;AAC9B,qBAAgB,MAAoB;;IAEvC,CAAC;AAEF,UAAO,EACL,MAAM,UAAU;AACd,UAAM,QAAQ,SAAS;MAE1B;;EAEJ"}
1
+ {"version":3,"file":"tool-calls.cjs","names":["namespaceKey","ToolCallAssembler","shouldIgnoreScopedTaskToolEvent","upsertToolCall","isRootNamespace","openProjectionSubscription"],"sources":["../../../src/stream/projections/tool-calls.ts"],"sourcesContent":["/**\n * Namespace-scoped `tools` projection.\n *\n * Opens `thread.subscribe({ channels: [\"tools\"], namespaces: [ns] })`,\n * feeds events through {@link ToolCallAssembler}, and surfaces an\n * array of {@link AssembledToolCall}s that grows as calls are\n * discovered. Each handle exposes reactive {@link AssembledToolCall.status},\n * {@link AssembledToolCall.error}, and {@link AssembledToolCall.output}\n * (`null` until the call succeeds).\n */\nimport type { ToolsEvent } from \"@langchain/protocol\";\nimport {\n shouldIgnoreScopedTaskToolEvent,\n ToolCallAssembler,\n} from \"../../client/stream/handles/tools.js\";\nimport type { AssembledToolCall } from \"../../client/stream/handles/tools.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { upsertToolCall } from \"../tool-calls.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function toolCallsProjection(\n namespace: readonly string[]\n): ProjectionSpec<AssembledToolCall[]> {\n const ns = [...namespace];\n const key = `toolCalls|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new ToolCallAssembler();\n\n const applyToolsEvent = (event: ToolsEvent): void => {\n if (shouldIgnoreScopedTaskToolEvent(ns, event)) return;\n const tc = assembler.consume(event);\n if (tc == null) return;\n const next = upsertToolCall(store.getSnapshot(), tc);\n store.setValue(next);\n };\n\n // See `messagesProjection` — root-scoped projections short-\n // circuit onto the root bus when the requested channels are\n // covered by the controller's root pump.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"tools\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"tools\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyToolsEvent(event as ToolsEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n let runtime: ProjectionRuntime | undefined;\n const openSubscription = () => {\n runtime = openProjectionSubscription({\n thread,\n channels: [\"tools\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"tools\") return;\n applyToolsEvent(event as ToolsEvent);\n },\n });\n };\n\n let disposed = false;\n void (async () => {\n const seeded =\n (await rootBus.trySeedFromHistory?.({\n kind: \"toolCalls\",\n namespace: ns,\n store,\n })) === true;\n if (!seeded && !disposed) openSubscription();\n })();\n\n return {\n async dispose() {\n disposed = true;\n await runtime?.dispose();\n },\n };\n },\n };\n}\n"],"mappings":";;;;;AAqBA,SAAgB,oBACd,WACqC;CACrC,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,aAAaA,kBAAAA,aAAa,GAAG;EAIvC,WAAW;EACX,SAAS,EAAE;EACX,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,YAAY,IAAIC,cAAAA,mBAAmB;GAEzC,MAAM,mBAAmB,UAA4B;AACnD,QAAIC,cAAAA,gCAAgC,IAAI,MAAM,CAAE;IAChD,MAAM,KAAK,UAAU,QAAQ,MAAM;AACnC,QAAI,MAAM,KAAM;IAChB,MAAM,OAAOC,mBAAAA,eAAe,MAAM,aAAa,EAAE,GAAG;AACpD,UAAM,SAAS,KAAK;;AAStB,OAFEC,kBAAAA,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,QAAQ,EAErC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,QAAS;AAC9B,SAAI,CAACA,kBAAAA,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,qBAAgB,MAAoB;MACpC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;GAGH,IAAI;GACJ,MAAM,yBAAyB;AAC7B,cAAUC,gBAAAA,2BAA2B;KACnC;KACA,UAAU,CAAC,QAAQ;KACnB,WAAW;KACX,QAAQ,OAAO;AACb,UAAI,MAAM,WAAW,QAAS;AAC9B,sBAAgB,MAAoB;;KAEvC,CAAC;;GAGJ,IAAI,WAAW;AACf,IAAM,YAAY;AAOhB,QAAI,EALD,MAAM,QAAQ,qBAAqB;KAClC,MAAM;KACN,WAAW;KACX;KACD,CAAC,KAAM,SACK,CAAC,SAAU,mBAAkB;OAC1C;AAEJ,UAAO,EACL,MAAM,UAAU;AACd,eAAW;AACX,UAAM,SAAS,SAAS;MAE3B;;EAEJ"}
@@ -28,17 +28,29 @@ function toolCallsProjection(namespace) {
28
28
  unsubscribe();
29
29
  } };
30
30
  }
31
- const runtime = openProjectionSubscription({
32
- thread,
33
- channels: ["tools"],
34
- namespace: ns,
35
- onEvent(event) {
36
- if (event.method !== "tools") return;
37
- applyToolsEvent(event);
38
- }
39
- });
31
+ let runtime;
32
+ const openSubscription = () => {
33
+ runtime = openProjectionSubscription({
34
+ thread,
35
+ channels: ["tools"],
36
+ namespace: ns,
37
+ onEvent(event) {
38
+ if (event.method !== "tools") return;
39
+ applyToolsEvent(event);
40
+ }
41
+ });
42
+ };
43
+ let disposed = false;
44
+ (async () => {
45
+ if (!(await rootBus.trySeedFromHistory?.({
46
+ kind: "toolCalls",
47
+ namespace: ns,
48
+ store
49
+ }) === true) && !disposed) openSubscription();
50
+ })();
40
51
  return { async dispose() {
41
- await runtime.dispose();
52
+ disposed = true;
53
+ await runtime?.dispose();
42
54
  } };
43
55
  }
44
56
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tool-calls.js","names":[],"sources":["../../../src/stream/projections/tool-calls.ts"],"sourcesContent":["/**\n * Namespace-scoped `tools` projection.\n *\n * Opens `thread.subscribe({ channels: [\"tools\"], namespaces: [ns] })`,\n * feeds events through {@link ToolCallAssembler}, and surfaces an\n * array of {@link AssembledToolCall}s that grows as calls are\n * discovered. Each handle exposes reactive {@link AssembledToolCall.status},\n * {@link AssembledToolCall.error}, and {@link AssembledToolCall.output}\n * (`null` until the call succeeds).\n */\nimport type { ToolsEvent } from \"@langchain/protocol\";\nimport {\n shouldIgnoreScopedTaskToolEvent,\n ToolCallAssembler,\n} from \"../../client/stream/handles/tools.js\";\nimport type { AssembledToolCall } from \"../../client/stream/handles/tools.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { upsertToolCall } from \"../tool-calls.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function toolCallsProjection(\n namespace: readonly string[]\n): ProjectionSpec<AssembledToolCall[]> {\n const ns = [...namespace];\n const key = `toolCalls|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new ToolCallAssembler();\n\n const applyToolsEvent = (event: ToolsEvent): void => {\n if (shouldIgnoreScopedTaskToolEvent(ns, event)) return;\n const tc = assembler.consume(event);\n if (tc == null) return;\n const next = upsertToolCall(store.getSnapshot(), tc);\n store.setValue(next);\n };\n\n // See `messagesProjection` — root-scoped projections short-\n // circuit onto the root bus when the requested channels are\n // covered by the controller's root pump.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"tools\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"tools\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyToolsEvent(event as ToolsEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n const runtime = openProjectionSubscription({\n thread,\n channels: [\"tools\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"tools\") return;\n applyToolsEvent(event as ToolsEvent);\n },\n });\n\n return {\n async dispose() {\n await runtime.dispose();\n },\n };\n },\n };\n}\n"],"mappings":";;;;;AAqBA,SAAgB,oBACd,WACqC;CACrC,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,aAAa,aAAa,GAAG;EAIvC,WAAW;EACX,SAAS,EAAE;EACX,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,YAAY,IAAI,mBAAmB;GAEzC,MAAM,mBAAmB,UAA4B;AACnD,QAAI,gCAAgC,IAAI,MAAM,CAAE;IAChD,MAAM,KAAK,UAAU,QAAQ,MAAM;AACnC,QAAI,MAAM,KAAM;IAChB,MAAM,OAAO,eAAe,MAAM,aAAa,EAAE,GAAG;AACpD,UAAM,SAAS,KAAK;;AAStB,OAFE,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,QAAQ,EAErC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,QAAS;AAC9B,SAAI,CAAC,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,qBAAgB,MAAoB;MACpC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;GAGH,MAAM,UAAU,2BAA2B;IACzC;IACA,UAAU,CAAC,QAAQ;IACnB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,QAAS;AAC9B,qBAAgB,MAAoB;;IAEvC,CAAC;AAEF,UAAO,EACL,MAAM,UAAU;AACd,UAAM,QAAQ,SAAS;MAE1B;;EAEJ"}
1
+ {"version":3,"file":"tool-calls.js","names":[],"sources":["../../../src/stream/projections/tool-calls.ts"],"sourcesContent":["/**\n * Namespace-scoped `tools` projection.\n *\n * Opens `thread.subscribe({ channels: [\"tools\"], namespaces: [ns] })`,\n * feeds events through {@link ToolCallAssembler}, and surfaces an\n * array of {@link AssembledToolCall}s that grows as calls are\n * discovered. Each handle exposes reactive {@link AssembledToolCall.status},\n * {@link AssembledToolCall.error}, and {@link AssembledToolCall.output}\n * (`null` until the call succeeds).\n */\nimport type { ToolsEvent } from \"@langchain/protocol\";\nimport {\n shouldIgnoreScopedTaskToolEvent,\n ToolCallAssembler,\n} from \"../../client/stream/handles/tools.js\";\nimport type { AssembledToolCall } from \"../../client/stream/handles/tools.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { upsertToolCall } from \"../tool-calls.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function toolCallsProjection(\n namespace: readonly string[]\n): ProjectionSpec<AssembledToolCall[]> {\n const ns = [...namespace];\n const key = `toolCalls|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: [],\n open({ thread, store, rootBus }): ProjectionRuntime {\n const assembler = new ToolCallAssembler();\n\n const applyToolsEvent = (event: ToolsEvent): void => {\n if (shouldIgnoreScopedTaskToolEvent(ns, event)) return;\n const tc = assembler.consume(event);\n if (tc == null) return;\n const next = upsertToolCall(store.getSnapshot(), tc);\n store.setValue(next);\n };\n\n // See `messagesProjection` — root-scoped projections short-\n // circuit onto the root bus when the requested channels are\n // covered by the controller's root pump.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"tools\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"tools\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyToolsEvent(event as ToolsEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n let runtime: ProjectionRuntime | undefined;\n const openSubscription = () => {\n runtime = openProjectionSubscription({\n thread,\n channels: [\"tools\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"tools\") return;\n applyToolsEvent(event as ToolsEvent);\n },\n });\n };\n\n let disposed = false;\n void (async () => {\n const seeded =\n (await rootBus.trySeedFromHistory?.({\n kind: \"toolCalls\",\n namespace: ns,\n store,\n })) === true;\n if (!seeded && !disposed) openSubscription();\n })();\n\n return {\n async dispose() {\n disposed = true;\n await runtime?.dispose();\n },\n };\n },\n };\n}\n"],"mappings":";;;;;AAqBA,SAAgB,oBACd,WACqC;CACrC,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,aAAa,aAAa,GAAG;EAIvC,WAAW;EACX,SAAS,EAAE;EACX,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,YAAY,IAAI,mBAAmB;GAEzC,MAAM,mBAAmB,UAA4B;AACnD,QAAI,gCAAgC,IAAI,MAAM,CAAE;IAChD,MAAM,KAAK,UAAU,QAAQ,MAAM;AACnC,QAAI,MAAM,KAAM;IAChB,MAAM,OAAO,eAAe,MAAM,aAAa,EAAE,GAAG;AACpD,UAAM,SAAS,KAAK;;AAStB,OAFE,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,QAAQ,EAErC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,QAAS;AAC9B,SAAI,CAAC,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,qBAAgB,MAAoB;MACpC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;GAGH,IAAI;GACJ,MAAM,yBAAyB;AAC7B,cAAU,2BAA2B;KACnC;KACA,UAAU,CAAC,QAAQ;KACnB,WAAW;KACX,QAAQ,OAAO;AACb,UAAI,MAAM,WAAW,QAAS;AAC9B,sBAAgB,MAAoB;;KAEvC,CAAC;;GAGJ,IAAI,WAAW;AACf,IAAM,YAAY;AAOhB,QAAI,EALD,MAAM,QAAQ,qBAAqB;KAClC,MAAM;KACN,WAAW;KACX;KACD,CAAC,KAAM,SACK,CAAC,SAAU,mBAAkB;OAC1C;AAEJ,UAAO,EACL,MAAM,UAAU;AACd,eAAW;AACX,UAAM,SAAS,SAAS;MAE3B;;EAEJ"}
@@ -1,4 +1,4 @@
1
- const require_messages = require("../../ui/messages.cjs");
1
+ const require_message_coercion = require("../message-coercion.cjs");
2
2
  const require_namespace = require("../namespace.cjs");
3
3
  const require_runtime = require("./runtime.cjs");
4
4
  //#region src/stream/projections/values.ts
@@ -43,7 +43,7 @@ function coerceMessagesInState(value, messagesKey) {
43
43
  if (!maybeMessages.some((m) => m != null && typeof m.getType !== "function")) return value;
44
44
  return {
45
45
  ...state,
46
- [messagesKey]: require_messages.ensureMessageInstances(maybeMessages)
46
+ [messagesKey]: require_message_coercion.ensureMessageInstances(maybeMessages)
47
47
  };
48
48
  }
49
49
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"values.cjs","names":["namespaceKey","isRootNamespace","openProjectionSubscription","ensureMessageInstances"],"sources":["../../../src/stream/projections/values.ts"],"sourcesContent":["/**\n * Namespace-scoped `values` projection.\n *\n * Opens `thread.subscribe({ channels: [\"values\"], namespaces: [ns] })`\n * and stores the most-recent `values.data` payload. Mirrors\n * {@link ThreadStream.values} but scoped to an arbitrary namespace so\n * subgraphs and subagents can expose their own state.\n *\n * When the state payload carries a `messages` array of serialized\n * messages, those are coerced to `BaseMessage` class instances so the\n * surface shape matches the root snapshot.\n */\nimport type { ValuesEvent } from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport {\n ensureMessageInstances,\n tryCoerceMessageLikeToMessage,\n} from \"../../ui/messages.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function valuesProjection<T = unknown>(\n namespace: readonly string[],\n messagesKey: string = \"messages\"\n): ProjectionSpec<T | undefined> {\n const ns = [...namespace];\n const key = `values|${messagesKey}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: undefined,\n open({ thread, store, rootBus }): ProjectionRuntime {\n const applyValuesEvent = (event: ValuesEvent): void => {\n const coerced = coerceMessagesInState(event.params.data, messagesKey);\n store.setValue(coerced as T);\n };\n\n // See `messagesProjection` — root-scoped projections attach to\n // the controller's root bus instead of opening a duplicate\n // server subscription.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"values\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"values\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyValuesEvent(event as ValuesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: [\"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"values\") return;\n applyValuesEvent(event as ValuesEvent);\n },\n });\n },\n };\n}\n\nfunction coerceMessagesInState(value: unknown, messagesKey: string): unknown {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n return value;\n }\n const state = value as Record<string, unknown>;\n const maybeMessages = state[messagesKey];\n if (!Array.isArray(maybeMessages) || maybeMessages.length === 0) {\n return value;\n }\n // Fast path: array already contains class instances.\n const hasPlain = maybeMessages.some(\n (m) => m != null && typeof (m as BaseMessage).getType !== \"function\"\n );\n if (!hasPlain) return value;\n return {\n ...state,\n [messagesKey]: ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n ),\n };\n}\n\nexport { tryCoerceMessageLikeToMessage };\n"],"mappings":";;;;AAuBA,SAAgB,iBACd,WACA,cAAsB,YACS;CAC/B,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,UAAU,YAAY,GAAGA,kBAAAA,aAAa,GAAG;EAInD,WAAW;EACX,SAAS,KAAA;EACT,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,oBAAoB,UAA6B;IACrD,MAAM,UAAU,sBAAsB,MAAM,OAAO,MAAM,YAAY;AACrE,UAAM,SAAS,QAAa;;AAS9B,OAFEC,kBAAAA,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,SAAS,EAEtC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,SAAU;AAC/B,SAAI,CAACA,kBAAAA,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,sBAAiB,MAAqB;MACtC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAOC,gBAAAA,2BAA2B;IAChC;IACA,UAAU,CAAC,SAAS;IACpB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,SAAU;AAC/B,sBAAiB,MAAqB;;IAEzC,CAAC;;EAEL;;AAGH,SAAS,sBAAsB,OAAgB,aAA8B;AAC3E,KAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACpE,QAAO;CAET,MAAM,QAAQ;CACd,MAAM,gBAAgB,MAAM;AAC5B,KAAI,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc,WAAW,EAC5D,QAAO;AAMT,KAAI,CAHa,cAAc,MAC5B,MAAM,KAAK,QAAQ,OAAQ,EAAkB,YAAY,WAC3D,CACc,QAAO;AACtB,QAAO;EACL,GAAG;GACF,cAAcC,iBAAAA,uBACb,cACD;EACF"}
1
+ {"version":3,"file":"values.cjs","names":["namespaceKey","isRootNamespace","openProjectionSubscription","ensureMessageInstances"],"sources":["../../../src/stream/projections/values.ts"],"sourcesContent":["/**\n * Namespace-scoped `values` projection.\n *\n * Opens `thread.subscribe({ channels: [\"values\"], namespaces: [ns] })`\n * and stores the most-recent `values.data` payload. Mirrors\n * {@link ThreadStream.values} but scoped to an arbitrary namespace so\n * subgraphs and subagents can expose their own state.\n *\n * When the state payload carries a `messages` array of serialized\n * messages, those are coerced to `BaseMessage` class instances so the\n * surface shape matches the root snapshot.\n */\nimport type { ValuesEvent } from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport {\n ensureMessageInstances,\n tryCoerceMessageLikeToMessage,\n} from \"../message-coercion.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function valuesProjection<T = unknown>(\n namespace: readonly string[],\n messagesKey: string = \"messages\"\n): ProjectionSpec<T | undefined> {\n const ns = [...namespace];\n const key = `values|${messagesKey}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: undefined,\n open({ thread, store, rootBus }): ProjectionRuntime {\n const applyValuesEvent = (event: ValuesEvent): void => {\n const coerced = coerceMessagesInState(event.params.data, messagesKey);\n store.setValue(coerced as T);\n };\n\n // See `messagesProjection` — root-scoped projections attach to\n // the controller's root bus instead of opening a duplicate\n // server subscription.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"values\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"values\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyValuesEvent(event as ValuesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: [\"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"values\") return;\n applyValuesEvent(event as ValuesEvent);\n },\n });\n },\n };\n}\n\nfunction coerceMessagesInState(value: unknown, messagesKey: string): unknown {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n return value;\n }\n const state = value as Record<string, unknown>;\n const maybeMessages = state[messagesKey];\n if (!Array.isArray(maybeMessages) || maybeMessages.length === 0) {\n return value;\n }\n // Fast path: array already contains class instances.\n const hasPlain = maybeMessages.some(\n (m) => m != null && typeof (m as BaseMessage).getType !== \"function\"\n );\n if (!hasPlain) return value;\n return {\n ...state,\n [messagesKey]: ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n ),\n };\n}\n\nexport { tryCoerceMessageLikeToMessage };\n"],"mappings":";;;;AAuBA,SAAgB,iBACd,WACA,cAAsB,YACS;CAC/B,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,UAAU,YAAY,GAAGA,kBAAAA,aAAa,GAAG;EAInD,WAAW;EACX,SAAS,KAAA;EACT,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,oBAAoB,UAA6B;IACrD,MAAM,UAAU,sBAAsB,MAAM,OAAO,MAAM,YAAY;AACrE,UAAM,SAAS,QAAa;;AAS9B,OAFEC,kBAAAA,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,SAAS,EAEtC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,SAAU;AAC/B,SAAI,CAACA,kBAAAA,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,sBAAiB,MAAqB;MACtC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAOC,gBAAAA,2BAA2B;IAChC;IACA,UAAU,CAAC,SAAS;IACpB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,SAAU;AAC/B,sBAAiB,MAAqB;;IAEzC,CAAC;;EAEL;;AAGH,SAAS,sBAAsB,OAAgB,aAA8B;AAC3E,KAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACpE,QAAO;CAET,MAAM,QAAQ;CACd,MAAM,gBAAgB,MAAM;AAC5B,KAAI,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc,WAAW,EAC5D,QAAO;AAMT,KAAI,CAHa,cAAc,MAC5B,MAAM,KAAK,QAAQ,OAAQ,EAAkB,YAAY,WAC3D,CACc,QAAO;AACtB,QAAO;EACL,GAAG;GACF,cAAcC,yBAAAA,uBACb,cACD;EACF"}
@@ -1,4 +1,4 @@
1
- import { ensureMessageInstances } from "../../ui/messages.js";
1
+ import { ensureMessageInstances } from "../message-coercion.js";
2
2
  import { isRootNamespace, namespaceKey } from "../namespace.js";
3
3
  import { openProjectionSubscription } from "./runtime.js";
4
4
  //#region src/stream/projections/values.ts
@@ -1 +1 @@
1
- {"version":3,"file":"values.js","names":[],"sources":["../../../src/stream/projections/values.ts"],"sourcesContent":["/**\n * Namespace-scoped `values` projection.\n *\n * Opens `thread.subscribe({ channels: [\"values\"], namespaces: [ns] })`\n * and stores the most-recent `values.data` payload. Mirrors\n * {@link ThreadStream.values} but scoped to an arbitrary namespace so\n * subgraphs and subagents can expose their own state.\n *\n * When the state payload carries a `messages` array of serialized\n * messages, those are coerced to `BaseMessage` class instances so the\n * surface shape matches the root snapshot.\n */\nimport type { ValuesEvent } from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport {\n ensureMessageInstances,\n tryCoerceMessageLikeToMessage,\n} from \"../../ui/messages.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function valuesProjection<T = unknown>(\n namespace: readonly string[],\n messagesKey: string = \"messages\"\n): ProjectionSpec<T | undefined> {\n const ns = [...namespace];\n const key = `values|${messagesKey}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: undefined,\n open({ thread, store, rootBus }): ProjectionRuntime {\n const applyValuesEvent = (event: ValuesEvent): void => {\n const coerced = coerceMessagesInState(event.params.data, messagesKey);\n store.setValue(coerced as T);\n };\n\n // See `messagesProjection` — root-scoped projections attach to\n // the controller's root bus instead of opening a duplicate\n // server subscription.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"values\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"values\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyValuesEvent(event as ValuesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: [\"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"values\") return;\n applyValuesEvent(event as ValuesEvent);\n },\n });\n },\n };\n}\n\nfunction coerceMessagesInState(value: unknown, messagesKey: string): unknown {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n return value;\n }\n const state = value as Record<string, unknown>;\n const maybeMessages = state[messagesKey];\n if (!Array.isArray(maybeMessages) || maybeMessages.length === 0) {\n return value;\n }\n // Fast path: array already contains class instances.\n const hasPlain = maybeMessages.some(\n (m) => m != null && typeof (m as BaseMessage).getType !== \"function\"\n );\n if (!hasPlain) return value;\n return {\n ...state,\n [messagesKey]: ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n ),\n };\n}\n\nexport { tryCoerceMessageLikeToMessage };\n"],"mappings":";;;;AAuBA,SAAgB,iBACd,WACA,cAAsB,YACS;CAC/B,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,UAAU,YAAY,GAAG,aAAa,GAAG;EAInD,WAAW;EACX,SAAS,KAAA;EACT,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,oBAAoB,UAA6B;IACrD,MAAM,UAAU,sBAAsB,MAAM,OAAO,MAAM,YAAY;AACrE,UAAM,SAAS,QAAa;;AAS9B,OAFE,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,SAAS,EAEtC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,SAAU;AAC/B,SAAI,CAAC,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,sBAAiB,MAAqB;MACtC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAO,2BAA2B;IAChC;IACA,UAAU,CAAC,SAAS;IACpB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,SAAU;AAC/B,sBAAiB,MAAqB;;IAEzC,CAAC;;EAEL;;AAGH,SAAS,sBAAsB,OAAgB,aAA8B;AAC3E,KAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACpE,QAAO;CAET,MAAM,QAAQ;CACd,MAAM,gBAAgB,MAAM;AAC5B,KAAI,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc,WAAW,EAC5D,QAAO;AAMT,KAAI,CAHa,cAAc,MAC5B,MAAM,KAAK,QAAQ,OAAQ,EAAkB,YAAY,WAC3D,CACc,QAAO;AACtB,QAAO;EACL,GAAG;GACF,cAAc,uBACb,cACD;EACF"}
1
+ {"version":3,"file":"values.js","names":[],"sources":["../../../src/stream/projections/values.ts"],"sourcesContent":["/**\n * Namespace-scoped `values` projection.\n *\n * Opens `thread.subscribe({ channels: [\"values\"], namespaces: [ns] })`\n * and stores the most-recent `values.data` payload. Mirrors\n * {@link ThreadStream.values} but scoped to an arbitrary namespace so\n * subgraphs and subagents can expose their own state.\n *\n * When the state payload carries a `messages` array of serialized\n * messages, those are coerced to `BaseMessage` class instances so the\n * surface shape matches the root snapshot.\n */\nimport type { ValuesEvent } from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport {\n ensureMessageInstances,\n tryCoerceMessageLikeToMessage,\n} from \"../message-coercion.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function valuesProjection<T = unknown>(\n namespace: readonly string[],\n messagesKey: string = \"messages\"\n): ProjectionSpec<T | undefined> {\n const ns = [...namespace];\n const key = `values|${messagesKey}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: undefined,\n open({ thread, store, rootBus }): ProjectionRuntime {\n const applyValuesEvent = (event: ValuesEvent): void => {\n const coerced = coerceMessagesInState(event.params.data, messagesKey);\n store.setValue(coerced as T);\n };\n\n // See `messagesProjection` — root-scoped projections attach to\n // the controller's root bus instead of opening a duplicate\n // server subscription.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"values\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"values\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyValuesEvent(event as ValuesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: [\"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"values\") return;\n applyValuesEvent(event as ValuesEvent);\n },\n });\n },\n };\n}\n\nfunction coerceMessagesInState(value: unknown, messagesKey: string): unknown {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n return value;\n }\n const state = value as Record<string, unknown>;\n const maybeMessages = state[messagesKey];\n if (!Array.isArray(maybeMessages) || maybeMessages.length === 0) {\n return value;\n }\n // Fast path: array already contains class instances.\n const hasPlain = maybeMessages.some(\n (m) => m != null && typeof (m as BaseMessage).getType !== \"function\"\n );\n if (!hasPlain) return value;\n return {\n ...state,\n [messagesKey]: ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n ),\n };\n}\n\nexport { tryCoerceMessageLikeToMessage };\n"],"mappings":";;;;AAuBA,SAAgB,iBACd,WACA,cAAsB,YACS;CAC/B,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,UAAU,YAAY,GAAG,aAAa,GAAG;EAInD,WAAW;EACX,SAAS,KAAA;EACT,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,oBAAoB,UAA6B;IACrD,MAAM,UAAU,sBAAsB,MAAM,OAAO,MAAM,YAAY;AACrE,UAAM,SAAS,QAAa;;AAS9B,OAFE,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,SAAS,EAEtC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,SAAU;AAC/B,SAAI,CAAC,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,sBAAiB,MAAqB;MACtC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAO,2BAA2B;IAChC;IACA,UAAU,CAAC,SAAS;IACpB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,SAAU;AAC/B,sBAAiB,MAAqB;;IAEzC,CAAC;;EAEL;;AAGH,SAAS,sBAAsB,OAAgB,aAA8B;AAC3E,KAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACpE,QAAO;CAET,MAAM,QAAQ;CACd,MAAM,gBAAgB,MAAM;AAC5B,KAAI,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc,WAAW,EAC5D,QAAO;AAMT,KAAI,CAHa,cAAc,MAC5B,MAAM,KAAK,QAAQ,OAAQ,EAAkB,YAAY,WAC3D,CACc,QAAO;AACtB,QAAO;EACL,GAAG;GACF,cAAc,uBACb,cACD;EACF"}