@langchain/langgraph 0.2.32 → 0.2.34

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.
@@ -2,12 +2,12 @@
2
2
  import { _coerceToRunnable, Runnable, } from "@langchain/core/runnables";
3
3
  import { isBaseChannel } from "../channels/base.js";
4
4
  import { END, CompiledGraph, Graph, START, Branch, } from "./graph.js";
5
- import { ChannelWrite, PASSTHROUGH, SKIP_WRITE, } from "../pregel/write.js";
5
+ import { ChannelWrite, PASSTHROUGH, } from "../pregel/write.js";
6
6
  import { ChannelRead, PregelNode } from "../pregel/read.js";
7
7
  import { NamedBarrierValue } from "../channels/named_barrier_value.js";
8
8
  import { EphemeralValue } from "../channels/ephemeral_value.js";
9
9
  import { RunnableCallable } from "../utils.js";
10
- import { _isCommand, _isSend, CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, Command, SELF, TAG_HIDDEN, } from "../constants.js";
10
+ import { isCommand, _isSend, CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, Command, SELF, TAG_HIDDEN, } from "../constants.js";
11
11
  import { InvalidUpdateError, ParentCommand } from "../errors.js";
12
12
  import { getChannel, } from "./annotation.js";
13
13
  import { isConfiguredManagedValue } from "../managed/base.js";
@@ -343,59 +343,101 @@ function _getChannels(schema) {
343
343
  */
344
344
  export class CompiledStateGraph extends CompiledGraph {
345
345
  attachNode(key, node) {
346
- const stateKeys = Object.keys(this.builder.channels);
346
+ let outputKeys;
347
+ if (key === START) {
348
+ // Get input schema keys excluding managed values
349
+ outputKeys = Object.entries(this.builder._schemaDefinitions.get(this.builder._inputDefinition))
350
+ .filter(([_, v]) => !isConfiguredManagedValue(v))
351
+ .map(([k]) => k);
352
+ }
353
+ else {
354
+ outputKeys = Object.keys(this.builder.channels);
355
+ }
356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
347
357
  function _getRoot(input) {
348
- if (_isCommand(input)) {
358
+ if (isCommand(input)) {
349
359
  if (input.graph === Command.PARENT) {
350
- return SKIP_WRITE;
360
+ return null;
361
+ }
362
+ return input._updateAsTuples();
363
+ }
364
+ else if (Array.isArray(input) &&
365
+ input.length > 0 &&
366
+ input.some((i) => isCommand(i))) {
367
+ const updates = [];
368
+ for (const i of input) {
369
+ if (isCommand(i)) {
370
+ if (i.graph === Command.PARENT) {
371
+ continue;
372
+ }
373
+ updates.push(...i._updateAsTuples());
374
+ }
375
+ else {
376
+ updates.push([ROOT, i]);
377
+ }
351
378
  }
352
- return input.update;
379
+ return updates;
353
380
  }
354
- return input;
381
+ else if (input != null) {
382
+ return [[ROOT, input]];
383
+ }
384
+ return null;
355
385
  }
356
386
  // to avoid name collision below
357
387
  const nodeKey = key;
358
- function getStateKey(key, input) {
388
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
+ function _getUpdates(input) {
359
390
  if (!input) {
360
- return SKIP_WRITE;
391
+ return null;
361
392
  }
362
- else if (_isCommand(input)) {
393
+ else if (isCommand(input)) {
363
394
  if (input.graph === Command.PARENT) {
364
- return SKIP_WRITE;
395
+ return null;
365
396
  }
366
- return getStateKey(key, input.update);
397
+ return input._updateAsTuples();
367
398
  }
368
- else if (typeof input !== "object" || Array.isArray(input)) {
399
+ else if (Array.isArray(input) &&
400
+ input.length > 0 &&
401
+ input.some(isCommand)) {
402
+ const updates = [];
403
+ for (const item of input) {
404
+ if (isCommand(item)) {
405
+ if (item.graph === Command.PARENT) {
406
+ continue;
407
+ }
408
+ updates.push(...item._updateAsTuples());
409
+ }
410
+ else {
411
+ const itemUpdates = _getUpdates(item);
412
+ if (itemUpdates) {
413
+ updates.push(...(itemUpdates ?? []));
414
+ }
415
+ }
416
+ }
417
+ return updates;
418
+ }
419
+ else if (typeof input === "object" && !Array.isArray(input)) {
420
+ return Object.entries(input).filter(([k]) => outputKeys.includes(k));
421
+ }
422
+ else {
369
423
  const typeofInput = Array.isArray(input) ? "array" : typeof input;
370
424
  throw new InvalidUpdateError(`Expected node "${nodeKey.toString()}" to return an object, received ${typeofInput}`, {
371
425
  lc_error_code: "INVALID_GRAPH_NODE_RETURN_VALUE",
372
426
  });
373
427
  }
374
- else {
375
- return key in input ? input[key] : SKIP_WRITE;
376
- }
377
428
  }
378
- // state updaters
379
- const stateWriteEntries = stateKeys.map((key) => key === ROOT
380
- ? {
381
- channel: key,
429
+ const stateWriteEntries = [
430
+ {
382
431
  value: PASSTHROUGH,
383
- skipNone: true,
384
432
  mapper: new RunnableCallable({
385
- func: _getRoot,
433
+ func: outputKeys.length && outputKeys[0] === ROOT
434
+ ? _getRoot
435
+ : _getUpdates,
386
436
  trace: false,
387
437
  recurse: false,
388
438
  }),
389
- }
390
- : {
391
- channel: key,
392
- value: PASSTHROUGH,
393
- mapper: new RunnableCallable({
394
- func: getStateKey.bind(null, key),
395
- trace: false,
396
- recurse: false,
397
- }),
398
- });
439
+ },
440
+ ];
399
441
  // add node and output channel
400
442
  if (key === START) {
401
443
  this.nodes[key] = new PregelNode({
@@ -536,7 +578,7 @@ function _controlBranch(value) {
536
578
  if (_isSend(value)) {
537
579
  return [value];
538
580
  }
539
- if (!_isCommand(value)) {
581
+ if (!isCommand(value)) {
540
582
  return [];
541
583
  }
542
584
  if (value.graph === Command.PARENT) {
@@ -108,7 +108,7 @@ function _getModelPreprocessingRunnable(stateModifier, messageModifier) {
108
108
  * ```
109
109
  */
110
110
  function createReactAgent(params) {
111
- const { llm, tools, messageModifier, stateModifier, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
111
+ const { llm, tools, messageModifier, stateModifier, stateSchema, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
112
112
  let toolClasses;
113
113
  if (!Array.isArray(tools)) {
114
114
  toolClasses = tools.tools;
@@ -138,7 +138,7 @@ function createReactAgent(params) {
138
138
  // TODO: Auto-promote streaming.
139
139
  return { messages: [await modelRunnable.invoke(state, config)] };
140
140
  };
141
- const workflow = new index_js_1.StateGraph(messages_annotation_js_1.MessagesAnnotation)
141
+ const workflow = new index_js_1.StateGraph(stateSchema ?? messages_annotation_js_1.MessagesAnnotation)
142
142
  .addNode("agent", callModel)
143
143
  .addNode("tools", new tool_node_js_1.ToolNode(toolClasses))
144
144
  .addEdge(index_js_1.START, "agent")
@@ -3,7 +3,7 @@ import { BaseMessage, BaseMessageLike, SystemMessage } from "@langchain/core/mes
3
3
  import { Runnable, RunnableToolLike } from "@langchain/core/runnables";
4
4
  import { StructuredToolInterface } from "@langchain/core/tools";
5
5
  import { All, BaseCheckpointSaver, BaseStore } from "@langchain/langgraph-checkpoint";
6
- import { START, CompiledStateGraph } from "../graph/index.js";
6
+ import { START, CompiledStateGraph, AnnotationRoot } from "../graph/index.js";
7
7
  import { MessagesAnnotation } from "../graph/messages_annotation.js";
8
8
  import { ToolNode } from "./tool_node.js";
9
9
  import { LangGraphRunnableConfig } from "../pregel/runnable_types.js";
@@ -14,11 +14,11 @@ export type N = typeof START | "agent" | "tools";
14
14
  export type StateModifier = SystemMessage | string | ((state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig) => BaseMessageLike[]) | ((state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig) => Promise<BaseMessageLike[]>) | Runnable;
15
15
  /** @deprecated Use StateModifier instead. */
16
16
  export type MessageModifier = SystemMessage | string | ((messages: BaseMessage[]) => BaseMessage[]) | ((messages: BaseMessage[]) => Promise<BaseMessage[]>) | Runnable;
17
- export type CreateReactAgentParams = {
17
+ export type CreateReactAgentParams<A extends AnnotationRoot<any> = AnnotationRoot<any>> = {
18
18
  /** The chat model that can utilize OpenAI-style tool calling. */
19
19
  llm: BaseChatModel;
20
20
  /** A list of tools or a ToolNode. */
21
- tools: ToolNode<typeof MessagesAnnotation.State> | (StructuredToolInterface | RunnableToolLike)[];
21
+ tools: ToolNode | (StructuredToolInterface | RunnableToolLike)[];
22
22
  /**
23
23
  * @deprecated
24
24
  * Use stateModifier instead. stateModifier works the same as
@@ -73,6 +73,7 @@ export type CreateReactAgentParams = {
73
73
  * - Runnable: This runnable should take in full graph state and the output is then passed to the language model.
74
74
  */
75
75
  stateModifier?: StateModifier;
76
+ stateSchema?: A;
76
77
  /** An optional checkpoint saver to persist the agent's state. */
77
78
  checkpointSaver?: BaseCheckpointSaver;
78
79
  /** An optional list of node names to interrupt before running. */
@@ -123,4 +124,4 @@ export type CreateReactAgentParams = {
123
124
  * // Returns the messages in the state at each step of execution
124
125
  * ```
125
126
  */
126
- export declare function createReactAgent(params: CreateReactAgentParams): CompiledStateGraph<(typeof MessagesAnnotation)["State"], (typeof MessagesAnnotation)["Update"], typeof START | "agent" | "tools">;
127
+ export declare function createReactAgent<A extends AnnotationRoot<any> = AnnotationRoot<any>>(params: CreateReactAgentParams<A>): CompiledStateGraph<(typeof MessagesAnnotation)["State"], (typeof MessagesAnnotation)["Update"], typeof START | "agent" | "tools", typeof MessagesAnnotation.spec & A["spec"], typeof MessagesAnnotation.spec & A["spec"]>;
@@ -1,6 +1,6 @@
1
1
  import { isAIMessage, isBaseMessage, SystemMessage, } from "@langchain/core/messages";
2
2
  import { Runnable, RunnableLambda, } from "@langchain/core/runnables";
3
- import { END, START, StateGraph } from "../graph/index.js";
3
+ import { END, START, StateGraph, } from "../graph/index.js";
4
4
  import { MessagesAnnotation } from "../graph/messages_annotation.js";
5
5
  import { ToolNode } from "./tool_node.js";
6
6
  function _convertMessageModifierToStateModifier(messageModifier) {
@@ -105,7 +105,7 @@ function _getModelPreprocessingRunnable(stateModifier, messageModifier) {
105
105
  * ```
106
106
  */
107
107
  export function createReactAgent(params) {
108
- const { llm, tools, messageModifier, stateModifier, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
108
+ const { llm, tools, messageModifier, stateModifier, stateSchema, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
109
109
  let toolClasses;
110
110
  if (!Array.isArray(tools)) {
111
111
  toolClasses = tools.tools;
@@ -135,7 +135,7 @@ export function createReactAgent(params) {
135
135
  // TODO: Auto-promote streaming.
136
136
  return { messages: [await modelRunnable.invoke(state, config)] };
137
137
  };
138
- const workflow = new StateGraph(MessagesAnnotation)
138
+ const workflow = new StateGraph(stateSchema ?? MessagesAnnotation)
139
139
  .addNode("agent", callModel)
140
140
  .addNode("tools", new ToolNode(toolClasses))
141
141
  .addEdge(START, "agent")
@@ -5,6 +5,7 @@ const messages_1 = require("@langchain/core/messages");
5
5
  const utils_js_1 = require("../utils.cjs");
6
6
  const graph_js_1 = require("../graph/graph.cjs");
7
7
  const errors_js_1 = require("../errors.cjs");
8
+ const constants_js_1 = require("../constants.cjs");
8
9
  /**
9
10
  * A node that runs the tools requested in the last AIMessage. It can be used
10
11
  * either in StateGraph with a "messages" key or in MessageGraph. If multiple
@@ -163,7 +164,8 @@ class ToolNode extends utils_js_1.RunnableCallable {
163
164
  throw new Error(`Tool "${call.name}" not found.`);
164
165
  }
165
166
  const output = await tool.invoke({ ...call, type: "tool_call" }, config);
166
- if ((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") {
167
+ if (((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") ||
168
+ (0, constants_js_1.isCommand)(output)) {
167
169
  return output;
168
170
  }
169
171
  else {
@@ -192,7 +194,18 @@ class ToolNode extends utils_js_1.RunnableCallable {
192
194
  });
193
195
  }
194
196
  }) ?? []);
195
- return (Array.isArray(input) ? outputs : { messages: outputs });
197
+ // Preserve existing behavior for non-command tool outputs for backwards compatibility
198
+ if (!outputs.some(constants_js_1.isCommand)) {
199
+ return (Array.isArray(input) ? outputs : { messages: outputs });
200
+ }
201
+ // Handle mixed Command and non-Command outputs
202
+ const combinedOutputs = outputs.map((output) => {
203
+ if ((0, constants_js_1.isCommand)(output)) {
204
+ return output;
205
+ }
206
+ return Array.isArray(input) ? [output] : { messages: [output] };
207
+ });
208
+ return combinedOutputs;
196
209
  }
197
210
  }
198
211
  exports.ToolNode = ToolNode;
@@ -2,6 +2,7 @@ import { ToolMessage, isBaseMessage, } from "@langchain/core/messages";
2
2
  import { RunnableCallable } from "../utils.js";
3
3
  import { END } from "../graph/graph.js";
4
4
  import { isGraphInterrupt } from "../errors.js";
5
+ import { isCommand } from "../constants.js";
5
6
  /**
6
7
  * A node that runs the tools requested in the last AIMessage. It can be used
7
8
  * either in StateGraph with a "messages" key or in MessageGraph. If multiple
@@ -160,7 +161,8 @@ export class ToolNode extends RunnableCallable {
160
161
  throw new Error(`Tool "${call.name}" not found.`);
161
162
  }
162
163
  const output = await tool.invoke({ ...call, type: "tool_call" }, config);
163
- if (isBaseMessage(output) && output._getType() === "tool") {
164
+ if ((isBaseMessage(output) && output._getType() === "tool") ||
165
+ isCommand(output)) {
164
166
  return output;
165
167
  }
166
168
  else {
@@ -189,7 +191,18 @@ export class ToolNode extends RunnableCallable {
189
191
  });
190
192
  }
191
193
  }) ?? []);
192
- return (Array.isArray(input) ? outputs : { messages: outputs });
194
+ // Preserve existing behavior for non-command tool outputs for backwards compatibility
195
+ if (!outputs.some(isCommand)) {
196
+ return (Array.isArray(input) ? outputs : { messages: outputs });
197
+ }
198
+ // Handle mixed Command and non-Command outputs
199
+ const combinedOutputs = outputs.map((output) => {
200
+ if (isCommand(output)) {
201
+ return output;
202
+ }
203
+ return Array.isArray(input) ? [output] : { messages: [output] };
204
+ });
205
+ return combinedOutputs;
193
206
  }
194
207
  }
195
208
  export function toolsCondition(state) {
@@ -587,8 +587,8 @@ class PregelLoop {
587
587
  const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
588
588
  (this.config.configurable?.[constants_js_1.CONFIG_KEY_RESUMING] !== undefined ||
589
589
  this.input === null ||
590
- (0, constants_js_1._isCommand)(this.input));
591
- if ((0, constants_js_1._isCommand)(this.input)) {
590
+ (0, constants_js_1.isCommand)(this.input));
591
+ if ((0, constants_js_1.isCommand)(this.input)) {
592
592
  const writes = {};
593
593
  // group writes by task id
594
594
  for (const [tid, key, value] of (0, io_js_1.mapCommand)(this.input, this.checkpointPendingWrites)) {
@@ -1,7 +1,7 @@
1
1
  import { IterableReadableStream } from "@langchain/core/utils/stream";
2
2
  import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, WRITES_IDX_MAP, } from "@langchain/langgraph-checkpoint";
3
3
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
4
- import { _isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, RESUME, TAG_HIDDEN, } from "../constants.js";
4
+ import { isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, RESUME, TAG_HIDDEN, } from "../constants.js";
5
5
  import { _applyWrites, _prepareNextTasks, increment, shouldInterrupt, } from "./algo.js";
6
6
  import { gatherIterator, gatherIteratorSync, prefixGenerator, } from "../utils.js";
7
7
  import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels, } from "./io.js";
@@ -583,8 +583,8 @@ export class PregelLoop {
583
583
  const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
584
584
  (this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined ||
585
585
  this.input === null ||
586
- _isCommand(this.input));
587
- if (_isCommand(this.input)) {
586
+ isCommand(this.input));
587
+ if (isCommand(this.input)) {
588
588
  const writes = {};
589
589
  // group writes by task id
590
590
  for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) {
@@ -12,6 +12,8 @@ function isChatGenerationChunk(x) {
12
12
  * A callback handler that implements stream_mode=messages.
13
13
  * Collects messages from (1) chat model stream events and (2) node outputs.
14
14
  */
15
+ // TODO: Make this import and explicitly implement the
16
+ // CallbackHandlerPrefersStreaming interface once we drop support for core 0.2
15
17
  class StreamMessagesHandler extends base_1.BaseCallbackHandler {
16
18
  constructor(streamFn) {
17
19
  super();
@@ -45,6 +47,12 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
45
47
  writable: true,
46
48
  value: {}
47
49
  });
50
+ Object.defineProperty(this, "lc_prefer_streaming", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: true
55
+ });
48
56
  this.streamFn = streamFn;
49
57
  }
50
58
  _emit(meta, message, dedupe = false) {
@@ -15,6 +15,7 @@ export declare class StreamMessagesHandler extends BaseCallbackHandler {
15
15
  metadatas: Record<string, Meta>;
16
16
  seen: Record<string, BaseMessage>;
17
17
  emittedChatModelRunIds: Record<string, boolean>;
18
+ lc_prefer_streaming: boolean;
18
19
  constructor(streamFn: (streamChunk: StreamChunk) => void);
19
20
  _emit(meta: Meta, message: BaseMessage, dedupe?: boolean): void;
20
21
  handleChatModelStart(_llm: Serialized, _messages: BaseMessage[][], runId: string, _parentRunId?: string, _extraParams?: Record<string, unknown>, tags?: string[], metadata?: Record<string, unknown>, name?: string): void;
@@ -9,6 +9,8 @@ function isChatGenerationChunk(x) {
9
9
  * A callback handler that implements stream_mode=messages.
10
10
  * Collects messages from (1) chat model stream events and (2) node outputs.
11
11
  */
12
+ // TODO: Make this import and explicitly implement the
13
+ // CallbackHandlerPrefersStreaming interface once we drop support for core 0.2
12
14
  export class StreamMessagesHandler extends BaseCallbackHandler {
13
15
  constructor(streamFn) {
14
16
  super();
@@ -42,6 +44,12 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
42
44
  writable: true,
43
45
  value: {}
44
46
  });
47
+ Object.defineProperty(this, "lc_prefer_streaming", {
48
+ enumerable: true,
49
+ configurable: true,
50
+ writable: true,
51
+ value: true
52
+ });
45
53
  this.streamFn = streamFn;
46
54
  }
47
55
  _emit(meta, message, dedupe = false) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChannelWrite = exports.PASSTHROUGH = exports.SKIP_WRITE = void 0;
4
+ const runnables_1 = require("@langchain/core/runnables");
4
5
  const constants_js_1 = require("../constants.cjs");
5
6
  const utils_js_1 = require("../utils.cjs");
6
7
  const errors_js_1 = require("../errors.cjs");
@@ -29,7 +30,13 @@ class ChannelWrite extends utils_js_1.RunnableCallable {
29
30
  constructor(writes, tags) {
30
31
  const name = `ChannelWrite<${writes
31
32
  .map((packet) => {
32
- return (0, constants_js_1._isSend)(packet) ? packet.node : packet.channel;
33
+ if ((0, constants_js_1._isSend)(packet)) {
34
+ return packet.node;
35
+ }
36
+ else if ("channel" in packet) {
37
+ return packet.channel;
38
+ }
39
+ return "...";
33
40
  })
34
41
  .join(",")}>`;
35
42
  super({
@@ -48,7 +55,13 @@ class ChannelWrite extends utils_js_1.RunnableCallable {
48
55
  }
49
56
  async _write(input, config) {
50
57
  const writes = this.writes.map((write) => {
51
- if (_isChannelWriteEntry(write) && _isPassthrough(write.value)) {
58
+ if (_isChannelWriteTupleEntry(write) && _isPassthrough(write.value)) {
59
+ return {
60
+ mapper: write.mapper,
61
+ value: input,
62
+ };
63
+ }
64
+ else if (_isChannelWriteEntry(write) && _isPassthrough(write.value)) {
52
65
  return {
53
66
  channel: write.channel,
54
67
  value: input,
@@ -65,36 +78,52 @@ class ChannelWrite extends utils_js_1.RunnableCallable {
65
78
  }
66
79
  // TODO: Support requireAtLeastOneOf
67
80
  static async doWrite(config, writes) {
68
- const sends = writes.filter(constants_js_1._isSend).map((packet) => {
69
- return [constants_js_1.TASKS, packet];
70
- });
71
- const entries = writes.filter((write) => {
72
- return !(0, constants_js_1._isSend)(write);
73
- });
74
- const invalidEntry = entries.find((write) => {
75
- return write.channel === constants_js_1.TASKS;
76
- });
77
- if (invalidEntry) {
78
- throw new errors_js_1.InvalidUpdateError(`Cannot write to the reserved channel ${constants_js_1.TASKS}`);
81
+ // validate
82
+ for (const w of writes) {
83
+ if (_isChannelWriteEntry(w)) {
84
+ if (w.channel === constants_js_1.TASKS) {
85
+ throw new errors_js_1.InvalidUpdateError("Cannot write to the reserved channel TASKS");
86
+ }
87
+ if (_isPassthrough(w.value)) {
88
+ throw new errors_js_1.InvalidUpdateError("PASSTHROUGH value must be replaced");
89
+ }
90
+ }
91
+ if (_isChannelWriteTupleEntry(w)) {
92
+ if (_isPassthrough(w.value)) {
93
+ throw new errors_js_1.InvalidUpdateError("PASSTHROUGH value must be replaced");
94
+ }
95
+ }
96
+ }
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ const writeEntries = [];
99
+ for (const w of writes) {
100
+ if ((0, constants_js_1._isSend)(w)) {
101
+ writeEntries.push([constants_js_1.TASKS, w]);
102
+ }
103
+ else if (_isChannelWriteTupleEntry(w)) {
104
+ const mappedResult = await w.mapper.invoke(w.value, config);
105
+ if (mappedResult != null && mappedResult.length > 0) {
106
+ writeEntries.push(...mappedResult);
107
+ }
108
+ }
109
+ else if (_isChannelWriteEntry(w)) {
110
+ const mappedValue = w.mapper !== undefined
111
+ ? await w.mapper.invoke(w.value, config)
112
+ : w.value;
113
+ if (_isSkipWrite(mappedValue)) {
114
+ continue;
115
+ }
116
+ if (w.skipNone && mappedValue === undefined) {
117
+ continue;
118
+ }
119
+ writeEntries.push([w.channel, mappedValue]);
120
+ }
121
+ else {
122
+ throw new Error(`Invalid write entry: ${JSON.stringify(w)}`);
123
+ }
79
124
  }
80
- const values = await Promise.all(entries.map(async (write) => {
81
- const mappedValue = write.mapper
82
- ? await write.mapper.invoke(write.value, config)
83
- : write.value;
84
- return {
85
- ...write,
86
- value: mappedValue,
87
- };
88
- })).then((writes) => {
89
- return writes
90
- .filter((write) => !write.skipNone || write.value !== null)
91
- .map((write) => {
92
- return [write.channel, write.value];
93
- });
94
- });
95
125
  const write = config.configurable?.[constants_js_1.CONFIG_KEY_SEND];
96
- const filtered = values.filter(([_, value]) => !_isSkipWrite(value));
97
- write([...sends, ...filtered]);
126
+ write(writeEntries);
98
127
  }
99
128
  static isWriter(runnable) {
100
129
  return (
@@ -110,3 +139,8 @@ exports.ChannelWrite = ChannelWrite;
110
139
  function _isChannelWriteEntry(x) {
111
140
  return (x !== undefined && typeof x.channel === "string");
112
141
  }
142
+ function _isChannelWriteTupleEntry(x) {
143
+ return (x !== undefined &&
144
+ !_isChannelWriteEntry(x) &&
145
+ runnables_1.Runnable.isRunnable(x.mapper));
146
+ }
@@ -12,10 +12,10 @@ export declare const PASSTHROUGH: {
12
12
  * or None to skip writing.
13
13
  */
14
14
  export declare class ChannelWrite<RunInput = any> extends RunnableCallable {
15
- writes: Array<ChannelWriteEntry | Send>;
16
- constructor(writes: Array<ChannelWriteEntry | Send>, tags?: string[]);
15
+ writes: Array<ChannelWriteEntry | ChannelWriteTupleEntry | Send>;
16
+ constructor(writes: Array<ChannelWriteEntry | ChannelWriteTupleEntry | Send>, tags?: string[]);
17
17
  _write(input: unknown, config: RunnableConfig): Promise<unknown>;
18
- static doWrite(config: RunnableConfig, writes: (ChannelWriteEntry | Send)[]): Promise<void>;
18
+ static doWrite(config: RunnableConfig, writes: (ChannelWriteEntry | ChannelWriteTupleEntry | Send)[]): Promise<void>;
19
19
  static isWriter(runnable: RunnableLike): runnable is ChannelWrite;
20
20
  static registerWriter<T extends Runnable>(runnable: T): T;
21
21
  }
@@ -25,3 +25,7 @@ export interface ChannelWriteEntry {
25
25
  skipNone?: boolean;
26
26
  mapper?: Runnable;
27
27
  }
28
+ export interface ChannelWriteTupleEntry {
29
+ value: unknown;
30
+ mapper: Runnable<any, [string, any][]>;
31
+ }