@langchain/langgraph 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +588 -0
- package/dist/channels/base.cjs +58 -0
- package/dist/channels/base.d.ts +46 -0
- package/dist/channels/base.js +50 -0
- package/dist/channels/binop.cjs +70 -0
- package/dist/channels/binop.d.ts +16 -0
- package/dist/channels/binop.js +66 -0
- package/dist/channels/index.cjs +9 -0
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.js +1 -0
- package/dist/channels/last_value.cjs +53 -0
- package/dist/channels/last_value.d.ts +12 -0
- package/dist/channels/last_value.js +49 -0
- package/dist/channels/topic.cjs +90 -0
- package/dist/channels/topic.d.ts +19 -0
- package/dist/channels/topic.js +86 -0
- package/dist/checkpoint/base.cjs +32 -0
- package/dist/checkpoint/base.d.ts +47 -0
- package/dist/checkpoint/base.js +27 -0
- package/dist/checkpoint/index.cjs +8 -0
- package/dist/checkpoint/index.d.ts +2 -0
- package/dist/checkpoint/index.js +2 -0
- package/dist/checkpoint/memory.cjs +35 -0
- package/dist/checkpoint/memory.d.ts +8 -0
- package/dist/checkpoint/memory.js +31 -0
- package/dist/constants.cjs +5 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/graph/graph.cjs +175 -0
- package/dist/graph/graph.d.ts +30 -0
- package/dist/graph/graph.js +171 -0
- package/dist/graph/index.cjs +9 -0
- package/dist/graph/index.d.ts +2 -0
- package/dist/graph/index.js +2 -0
- package/dist/graph/state.cjs +108 -0
- package/dist/graph/state.d.ts +17 -0
- package/dist/graph/state.js +104 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/prebuilt/agent_executor.cjs +96 -0
- package/dist/prebuilt/agent_executor.d.ts +12 -0
- package/dist/prebuilt/agent_executor.js +92 -0
- package/dist/prebuilt/chat_agent_executor.cjs +130 -0
- package/dist/prebuilt/chat_agent_executor.d.ts +6 -0
- package/dist/prebuilt/chat_agent_executor.js +126 -0
- package/dist/prebuilt/index.cjs +9 -0
- package/dist/prebuilt/index.d.ts +3 -0
- package/dist/prebuilt/index.js +3 -0
- package/dist/prebuilt/tool_executor.cjs +63 -0
- package/dist/prebuilt/tool_executor.d.ts +27 -0
- package/dist/prebuilt/tool_executor.js +59 -0
- package/dist/pregel/debug.cjs +46 -0
- package/dist/pregel/debug.d.ts +4 -0
- package/dist/pregel/debug.js +41 -0
- package/dist/pregel/index.cjs +475 -0
- package/dist/pregel/index.d.ts +75 -0
- package/dist/pregel/index.js +469 -0
- package/dist/pregel/io.cjs +57 -0
- package/dist/pregel/io.d.ts +9 -0
- package/dist/pregel/io.js +52 -0
- package/dist/pregel/read.cjs +217 -0
- package/dist/pregel/read.d.ts +43 -0
- package/dist/pregel/read.js +211 -0
- package/dist/pregel/reserved.cjs +7 -0
- package/dist/pregel/reserved.d.ts +3 -0
- package/dist/pregel/reserved.js +4 -0
- package/dist/pregel/validate.cjs +90 -0
- package/dist/pregel/validate.d.ts +15 -0
- package/dist/pregel/validate.js +85 -0
- package/dist/pregel/write.cjs +54 -0
- package/dist/pregel/write.d.ts +13 -0
- package/dist/pregel/write.js +50 -0
- package/index.cjs +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +100 -0
- package/prebuilt.cjs +1 -0
- package/prebuilt.d.ts +1 -0
- package/prebuilt.js +1 -0
- package/pregel.cjs +1 -0
- package/pregel.d.ts +1 -0
- package/pregel.js +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Runnable, RunnableConfig } from "@langchain/core/runnables";
|
|
2
|
+
import { CallbackManagerForChainRun } from "@langchain/core/callbacks/manager";
|
|
3
|
+
import { IterableReadableStream } from "@langchain/core/utils/stream";
|
|
4
|
+
import { BaseChannel } from "../channels/base.js";
|
|
5
|
+
import { BaseCheckpointSaver } from "../checkpoint/base.js";
|
|
6
|
+
import { ChannelBatch, ChannelInvoke } from "./read.js";
|
|
7
|
+
import { ChannelWrite } from "./write.js";
|
|
8
|
+
export declare class GraphRecursionError extends Error {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class Channel {
|
|
12
|
+
static subscribeTo(channels: string, options?: {
|
|
13
|
+
key?: string;
|
|
14
|
+
when?: (arg: any) => boolean;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
}): ChannelInvoke;
|
|
17
|
+
static subscribeTo(channels: string[], options?: {
|
|
18
|
+
key?: string;
|
|
19
|
+
when?: (arg: any) => boolean;
|
|
20
|
+
tags?: string[];
|
|
21
|
+
}): ChannelInvoke;
|
|
22
|
+
static subscribeToEach(inbox: string, key?: string): ChannelBatch;
|
|
23
|
+
static writeTo(...args: any[]): ChannelWrite;
|
|
24
|
+
}
|
|
25
|
+
export interface PregelInterface {
|
|
26
|
+
/**
|
|
27
|
+
* @default {}
|
|
28
|
+
*/
|
|
29
|
+
channels?: Record<string, BaseChannel>;
|
|
30
|
+
/**
|
|
31
|
+
* @default "output"
|
|
32
|
+
*/
|
|
33
|
+
output?: string | Array<string>;
|
|
34
|
+
/**
|
|
35
|
+
* @default "input"
|
|
36
|
+
*/
|
|
37
|
+
input?: string | Array<string>;
|
|
38
|
+
/**
|
|
39
|
+
* @default []
|
|
40
|
+
*/
|
|
41
|
+
hidden?: Array<string>;
|
|
42
|
+
/**
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
debug?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* @default []
|
|
48
|
+
*/
|
|
49
|
+
interrupt?: string[];
|
|
50
|
+
nodes: Record<string, ChannelInvoke | ChannelBatch>;
|
|
51
|
+
checkpointer?: BaseCheckpointSaver;
|
|
52
|
+
stepTimeout?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface PregelOptions extends RunnableConfig {
|
|
55
|
+
outputKeys?: string | string[];
|
|
56
|
+
}
|
|
57
|
+
export type PregelInputType = any;
|
|
58
|
+
export type PregelOutputType = any;
|
|
59
|
+
export declare class Pregel extends Runnable<PregelInputType, PregelOutputType, PregelOptions> implements PregelInterface {
|
|
60
|
+
lc_namespace: string[];
|
|
61
|
+
channels: Record<string, BaseChannel>;
|
|
62
|
+
output: string | Array<string>;
|
|
63
|
+
input: string | Array<string>;
|
|
64
|
+
hidden: Array<string>;
|
|
65
|
+
debug: boolean;
|
|
66
|
+
nodes: Record<string, ChannelInvoke | ChannelBatch>;
|
|
67
|
+
checkpointer?: BaseCheckpointSaver;
|
|
68
|
+
stepTimeout?: number;
|
|
69
|
+
interrupt: string[];
|
|
70
|
+
constructor(fields: PregelInterface);
|
|
71
|
+
_transform(input: AsyncGenerator<PregelInputType>, runManager?: CallbackManagerForChainRun, config?: RunnableConfig & Partial<Record<string, unknown>>): AsyncGenerator<PregelOutputType>;
|
|
72
|
+
invoke(input: PregelInputType, config?: PregelOptions): Promise<PregelOutputType>;
|
|
73
|
+
stream(input: PregelInputType, config?: PregelOptions): Promise<IterableReadableStream<PregelOutputType>>;
|
|
74
|
+
transform(generator: AsyncGenerator<PregelInputType>, config?: PregelOptions): AsyncGenerator<PregelOutputType>;
|
|
75
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
import { Runnable, _coerceToRunnable, patchConfig, } from "@langchain/core/runnables";
|
|
3
|
+
import { IterableReadableStream } from "@langchain/core/utils/stream";
|
|
4
|
+
import { EmptyChannelError, createCheckpoint, emptyChannels, } from "../channels/base.js";
|
|
5
|
+
import { CheckpointAt, emptyCheckpoint, } from "../checkpoint/base.js";
|
|
6
|
+
import { ChannelBatch, ChannelInvoke } from "./read.js";
|
|
7
|
+
import { validateGraph } from "./validate.js";
|
|
8
|
+
import { ReservedChannels } from "./reserved.js";
|
|
9
|
+
import { mapInput, mapOutput } from "./io.js";
|
|
10
|
+
import { ChannelWrite } from "./write.js";
|
|
11
|
+
import { CONFIG_KEY_READ, CONFIG_KEY_SEND } from "../constants.js";
|
|
12
|
+
const DEFAULT_RECURSION_LIMIT = 25;
|
|
13
|
+
export class GraphRecursionError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "GraphRecursionError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function _coerceWriteValue(value) {
|
|
20
|
+
if (!Runnable.isRunnable(value) && typeof value !== "function") {
|
|
21
|
+
return _coerceToRunnable(() => value);
|
|
22
|
+
}
|
|
23
|
+
return _coerceToRunnable(value);
|
|
24
|
+
}
|
|
25
|
+
function isString(value) {
|
|
26
|
+
return typeof value === "string";
|
|
27
|
+
}
|
|
28
|
+
export class Channel {
|
|
29
|
+
static subscribeTo(channels, options) {
|
|
30
|
+
const { key, when, tags } = options ?? {};
|
|
31
|
+
if (Array.isArray(channels) && key !== undefined) {
|
|
32
|
+
throw new Error("Can't specify a key when subscribing to multiple channels");
|
|
33
|
+
}
|
|
34
|
+
let channelMappingOrString;
|
|
35
|
+
if (isString(channels)) {
|
|
36
|
+
if (key) {
|
|
37
|
+
channelMappingOrString = { [key]: channels };
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
channelMappingOrString = channels;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
channelMappingOrString = Object.fromEntries(channels.map((chan) => [chan, chan]));
|
|
45
|
+
}
|
|
46
|
+
const triggers = Array.isArray(channels) ? channels : [channels];
|
|
47
|
+
return new ChannelInvoke({
|
|
48
|
+
channels: channelMappingOrString,
|
|
49
|
+
triggers,
|
|
50
|
+
when,
|
|
51
|
+
tags,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
static subscribeToEach(inbox, key) {
|
|
55
|
+
return new ChannelBatch({
|
|
56
|
+
channel: inbox,
|
|
57
|
+
key,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
static writeTo(...args) {
|
|
62
|
+
// const channelPairs: Array<[string, WriteValue<RunInput, RunOutput>]> =
|
|
63
|
+
// channels.map((c) => [c, undefined]);
|
|
64
|
+
// return new ChannelWrite<RunInput, RunOutput>(channelPairs);
|
|
65
|
+
const channelPairs = [];
|
|
66
|
+
if (args.length === 1 && typeof args[0] === "object") {
|
|
67
|
+
// Handle the case where named arguments are passed as an object
|
|
68
|
+
const additionalArgs = args[0];
|
|
69
|
+
Object.entries(additionalArgs).forEach(([key, value]) => {
|
|
70
|
+
channelPairs.push([key, _coerceWriteValue(value)]);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
args.forEach((channel) => {
|
|
75
|
+
if (typeof channel === "string") {
|
|
76
|
+
channelPairs.push([channel, undefined]);
|
|
77
|
+
}
|
|
78
|
+
else if (typeof channel === "object") {
|
|
79
|
+
Object.entries(channel).forEach(([key, value]) => {
|
|
80
|
+
channelPairs.push([key, _coerceWriteValue(value)]);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return new ChannelWrite(channelPairs);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export class Pregel extends Runnable {
|
|
89
|
+
constructor(fields) {
|
|
90
|
+
super();
|
|
91
|
+
// Because Pregel extends `Runnable`.
|
|
92
|
+
Object.defineProperty(this, "lc_namespace", {
|
|
93
|
+
enumerable: true,
|
|
94
|
+
configurable: true,
|
|
95
|
+
writable: true,
|
|
96
|
+
value: ["langgraph", "pregel"]
|
|
97
|
+
});
|
|
98
|
+
Object.defineProperty(this, "channels", {
|
|
99
|
+
enumerable: true,
|
|
100
|
+
configurable: true,
|
|
101
|
+
writable: true,
|
|
102
|
+
value: {}
|
|
103
|
+
});
|
|
104
|
+
Object.defineProperty(this, "output", {
|
|
105
|
+
enumerable: true,
|
|
106
|
+
configurable: true,
|
|
107
|
+
writable: true,
|
|
108
|
+
value: "output"
|
|
109
|
+
});
|
|
110
|
+
Object.defineProperty(this, "input", {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
writable: true,
|
|
114
|
+
value: "input"
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(this, "hidden", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
configurable: true,
|
|
119
|
+
writable: true,
|
|
120
|
+
value: []
|
|
121
|
+
});
|
|
122
|
+
Object.defineProperty(this, "debug", {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
configurable: true,
|
|
125
|
+
writable: true,
|
|
126
|
+
value: false
|
|
127
|
+
});
|
|
128
|
+
Object.defineProperty(this, "nodes", {
|
|
129
|
+
enumerable: true,
|
|
130
|
+
configurable: true,
|
|
131
|
+
writable: true,
|
|
132
|
+
value: void 0
|
|
133
|
+
});
|
|
134
|
+
Object.defineProperty(this, "checkpointer", {
|
|
135
|
+
enumerable: true,
|
|
136
|
+
configurable: true,
|
|
137
|
+
writable: true,
|
|
138
|
+
value: void 0
|
|
139
|
+
});
|
|
140
|
+
Object.defineProperty(this, "stepTimeout", {
|
|
141
|
+
enumerable: true,
|
|
142
|
+
configurable: true,
|
|
143
|
+
writable: true,
|
|
144
|
+
value: void 0
|
|
145
|
+
});
|
|
146
|
+
Object.defineProperty(this, "interrupt", {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
configurable: true,
|
|
149
|
+
writable: true,
|
|
150
|
+
value: []
|
|
151
|
+
});
|
|
152
|
+
this.channels = fields.channels ?? this.channels;
|
|
153
|
+
this.output = fields.output ?? this.output;
|
|
154
|
+
this.input = fields.input ?? this.input;
|
|
155
|
+
this.hidden = fields.hidden ?? this.hidden;
|
|
156
|
+
this.debug = fields.debug ?? this.debug;
|
|
157
|
+
this.nodes = fields.nodes;
|
|
158
|
+
this.checkpointer = fields.checkpointer;
|
|
159
|
+
this.stepTimeout = fields.stepTimeout;
|
|
160
|
+
this.interrupt = fields.interrupt ?? this.interrupt;
|
|
161
|
+
// Bind the method to the instance
|
|
162
|
+
this._transform = this._transform.bind(this);
|
|
163
|
+
validateGraph({
|
|
164
|
+
nodes: this.nodes,
|
|
165
|
+
channels: this.channels,
|
|
166
|
+
output: this.output,
|
|
167
|
+
input: this.input,
|
|
168
|
+
hidden: this.hidden,
|
|
169
|
+
interrupt: this.interrupt,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async *_transform(input, runManager, config = {}) {
|
|
173
|
+
// assign defaults
|
|
174
|
+
let outputKeys = [];
|
|
175
|
+
if (Array.isArray(config.outputKeys) ||
|
|
176
|
+
typeof config.outputKeys === "string") {
|
|
177
|
+
outputKeys = config.outputKeys;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
for (const chan in this.channels) {
|
|
181
|
+
if (!this.hidden.includes(chan)) {
|
|
182
|
+
outputKeys.push(chan);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// copy nodes to ignore mutations during execution
|
|
187
|
+
const processes = { ...this.nodes };
|
|
188
|
+
// get checkpoint, or create an empty one
|
|
189
|
+
let checkpoint;
|
|
190
|
+
if (this.checkpointer) {
|
|
191
|
+
checkpoint = this.checkpointer.get(config);
|
|
192
|
+
}
|
|
193
|
+
checkpoint = checkpoint ?? emptyCheckpoint();
|
|
194
|
+
// create channels from checkpoint
|
|
195
|
+
const channels = emptyChannels(this.channels, checkpoint);
|
|
196
|
+
// map inputs to channel updates
|
|
197
|
+
const thisInput = this.input;
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
|
+
const inputPendingWrites = [];
|
|
200
|
+
for await (const c of input) {
|
|
201
|
+
for (const value of mapInput(thisInput, c)) {
|
|
202
|
+
inputPendingWrites.push(value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
_applyWrites(checkpoint, channels, inputPendingWrites, config, 0);
|
|
206
|
+
const read = (chan) => _readChannel(channels, chan);
|
|
207
|
+
// Similarly to Bulk Synchronous Parallel / Pregel model
|
|
208
|
+
// computation proceeds in steps, while there are channel updates
|
|
209
|
+
// channel updates from step N are only visible in step N+1
|
|
210
|
+
// channels are guaranteed to be immutable for the duration of the step,
|
|
211
|
+
// with channel updates applied only at the transition between steps
|
|
212
|
+
const recursionLimit = config.recursionLimit ?? DEFAULT_RECURSION_LIMIT;
|
|
213
|
+
for (let step = 0; step < recursionLimit + 1; step += 1) {
|
|
214
|
+
const nextTasks = _prepareNextTasks(checkpoint, processes, channels);
|
|
215
|
+
// if no more tasks, we're done
|
|
216
|
+
if (nextTasks.length === 0) {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
else if (step === config.recursionLimit) {
|
|
220
|
+
throw new GraphRecursionError(`Recursion limit of ${config.recursionLimit} reached without hitting a stop condition. You can increase the limit by setting the "recursionLimit" config key.`);
|
|
221
|
+
}
|
|
222
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
223
|
+
const pendingWrites = [];
|
|
224
|
+
const tasksWithConfig = nextTasks.map(([proc, input, name]) => [
|
|
225
|
+
proc,
|
|
226
|
+
input,
|
|
227
|
+
patchConfig(config, {
|
|
228
|
+
callbacks: runManager?.getChild(`graph:step:${step}`),
|
|
229
|
+
runName: name,
|
|
230
|
+
configurable: {
|
|
231
|
+
...config.configurable,
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
+
[CONFIG_KEY_SEND]: (items) => pendingWrites.push(...items),
|
|
234
|
+
[CONFIG_KEY_READ]: read,
|
|
235
|
+
},
|
|
236
|
+
}),
|
|
237
|
+
]);
|
|
238
|
+
// execute tasks, and wait for one to fail or all to finish.
|
|
239
|
+
// each task is independent from all other concurrent tasks
|
|
240
|
+
const tasks = tasksWithConfig.map(([proc, input, updatedConfig]) => () => proc.invoke(input, updatedConfig));
|
|
241
|
+
await executeTasks(tasks, this.stepTimeout);
|
|
242
|
+
// apply writes to channels
|
|
243
|
+
_applyWrites(checkpoint, channels, pendingWrites, config, step + 1);
|
|
244
|
+
// yield current value and checkpoint view
|
|
245
|
+
const stepOutput = mapOutput(outputKeys, pendingWrites, channels);
|
|
246
|
+
if (stepOutput) {
|
|
247
|
+
yield stepOutput;
|
|
248
|
+
if (typeof outputKeys !== "string") {
|
|
249
|
+
_applyWritesFromView(checkpoint, channels, stepOutput);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// save end of step checkpoint
|
|
253
|
+
if (this.checkpointer &&
|
|
254
|
+
this.checkpointer.at === CheckpointAt.END_OF_STEP) {
|
|
255
|
+
checkpoint = await createCheckpoint(checkpoint, channels);
|
|
256
|
+
this.checkpointer.put(config, checkpoint);
|
|
257
|
+
}
|
|
258
|
+
// interrupt if any channel written to is in interrupt list
|
|
259
|
+
if (pendingWrites.some(([chan]) => this.interrupt?.some((i) => i === chan))) {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// save end of run checkpoint
|
|
264
|
+
if (this.checkpointer && this.checkpointer.at === CheckpointAt.END_OF_RUN) {
|
|
265
|
+
checkpoint = await createCheckpoint(checkpoint, channels);
|
|
266
|
+
this.checkpointer.put(config, checkpoint);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async invoke(input, config) {
|
|
270
|
+
if (!config?.outputKeys) {
|
|
271
|
+
if (!config) {
|
|
272
|
+
config = {};
|
|
273
|
+
}
|
|
274
|
+
config.outputKeys = this.output;
|
|
275
|
+
}
|
|
276
|
+
let latest;
|
|
277
|
+
for await (const chunk of await this.stream(input, config)) {
|
|
278
|
+
latest = chunk;
|
|
279
|
+
}
|
|
280
|
+
if (!latest) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
return latest;
|
|
284
|
+
}
|
|
285
|
+
async stream(input, config) {
|
|
286
|
+
const inputIterator = (async function* () {
|
|
287
|
+
yield input;
|
|
288
|
+
})();
|
|
289
|
+
return IterableReadableStream.fromAsyncGenerator(this.transform(inputIterator, config));
|
|
290
|
+
}
|
|
291
|
+
async *transform(generator, config) {
|
|
292
|
+
for await (const chunk of this._transformStreamWithConfig(generator, this._transform, config)) {
|
|
293
|
+
yield chunk;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function timeout(ms) {
|
|
298
|
+
return new Promise((reject) => {
|
|
299
|
+
setTimeout(reject, ms);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
async function executeTasks(tasks, stepTimeout) {
|
|
303
|
+
// Wrap each task in a Promise that respects the step timeout
|
|
304
|
+
const wrappedTasks = tasks.map((task) => stepTimeout
|
|
305
|
+
? Promise.race([
|
|
306
|
+
task(),
|
|
307
|
+
stepTimeout ? timeout(stepTimeout) : Promise.resolve(),
|
|
308
|
+
])
|
|
309
|
+
: task());
|
|
310
|
+
// Wait for all tasks to settle
|
|
311
|
+
const results = await Promise.allSettled(wrappedTasks);
|
|
312
|
+
// Process the results
|
|
313
|
+
for (const result of results) {
|
|
314
|
+
if (result.status === "rejected") {
|
|
315
|
+
// If any task failed, cancel all pending tasks and throw the error
|
|
316
|
+
throw result.reason;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function _readChannel(channels, chan) {
|
|
321
|
+
try {
|
|
322
|
+
return channels[chan].get();
|
|
323
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
324
|
+
}
|
|
325
|
+
catch (e) {
|
|
326
|
+
if (e.name === EmptyChannelError.name) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
throw e;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function _applyWrites(checkpoint, channels,
|
|
333
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
334
|
+
pendingWrites, config, forStep) {
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
336
|
+
const pendingWritesByChannel = {};
|
|
337
|
+
// Group writes by channel
|
|
338
|
+
for (const [chan, val] of pendingWrites) {
|
|
339
|
+
for (const c in ReservedChannels) {
|
|
340
|
+
if (chan === c) {
|
|
341
|
+
throw new Error(`Can't write to reserved channel ${chan}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (chan in pendingWritesByChannel) {
|
|
345
|
+
pendingWritesByChannel[chan].push(val);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
pendingWritesByChannel[chan] = [val];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// Update reserved channels
|
|
352
|
+
pendingWritesByChannel[ReservedChannels.isLastStep] = [
|
|
353
|
+
forStep + 1 === config.recursionLimit,
|
|
354
|
+
];
|
|
355
|
+
const updatedChannels = new Set();
|
|
356
|
+
// Apply writes to channels
|
|
357
|
+
for (const chan in pendingWritesByChannel) {
|
|
358
|
+
if (chan in pendingWritesByChannel) {
|
|
359
|
+
const vals = pendingWritesByChannel[chan];
|
|
360
|
+
if (chan in channels) {
|
|
361
|
+
channels[chan].update(vals);
|
|
362
|
+
if (checkpoint.channelVersions[chan] === undefined) {
|
|
363
|
+
checkpoint.channelVersions[chan] = 1;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
checkpoint.channelVersions[chan] += 1;
|
|
367
|
+
}
|
|
368
|
+
updatedChannels.add(chan);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.warn(`Skipping write for channel ${chan} which has no readers`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Channels that weren't updated in this step are notified of a new step
|
|
376
|
+
for (const chan in channels) {
|
|
377
|
+
if (!updatedChannels.has(chan)) {
|
|
378
|
+
channels[chan].update([]);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function _applyWritesFromView(checkpoint, channels, values) {
|
|
383
|
+
for (const [chan, val] of Object.entries(values)) {
|
|
384
|
+
if (val === _readChannel(channels, chan)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (channels[chan].lc_graph_name === "LastValue") {
|
|
388
|
+
throw new Error(`Can't modify channel ${chan} with LastValue`);
|
|
389
|
+
}
|
|
390
|
+
checkpoint.channelVersions[chan] += 1;
|
|
391
|
+
channels[chan].update([values[chan]]);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function _prepareNextTasks(checkpoint, processes, channels) {
|
|
395
|
+
const tasks = [];
|
|
396
|
+
// Check if any processes should be run in next step
|
|
397
|
+
// If so, prepare the values to be passed to them
|
|
398
|
+
for (const name in processes) {
|
|
399
|
+
if (Object.prototype.hasOwnProperty.call(processes, name)) {
|
|
400
|
+
const proc = processes[name];
|
|
401
|
+
let seen = checkpoint.versionsSeen[name];
|
|
402
|
+
if (!seen) {
|
|
403
|
+
checkpoint.versionsSeen[name] = {};
|
|
404
|
+
seen = checkpoint.versionsSeen[name];
|
|
405
|
+
}
|
|
406
|
+
if ("triggers" in proc) {
|
|
407
|
+
// If any of the channels read by this process were updated
|
|
408
|
+
if (proc.triggers.some((chan) => checkpoint.channelVersions[chan] > (seen[chan] ?? 0))) {
|
|
409
|
+
// If all channels subscribed by this process have been initialized
|
|
410
|
+
try {
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
412
|
+
let val = {};
|
|
413
|
+
if (typeof proc.channels === "string") {
|
|
414
|
+
val[proc.channels] = _readChannel(channels, proc.channels);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
for (const [k, chan] of Object.entries(proc.channels)) {
|
|
418
|
+
val[k] = _readChannel(channels, chan);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Processes that subscribe to a single keyless channel get
|
|
422
|
+
// the value directly, instead of a dict
|
|
423
|
+
if (typeof proc.channels === "string") {
|
|
424
|
+
val = val[proc.channels];
|
|
425
|
+
}
|
|
426
|
+
else if (Object.keys(proc.channels).length === 1 &&
|
|
427
|
+
proc.channels[Object.keys(proc.channels)[0]] === undefined) {
|
|
428
|
+
val = val[Object.keys(proc.channels)[0]];
|
|
429
|
+
}
|
|
430
|
+
// Update seen versions
|
|
431
|
+
proc.triggers.forEach((chan) => {
|
|
432
|
+
const version = checkpoint.channelVersions[chan];
|
|
433
|
+
if (version !== undefined) {
|
|
434
|
+
seen[chan] = version;
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
// skip if condition is not met
|
|
438
|
+
if (proc.when === undefined || proc.when(val)) {
|
|
439
|
+
tasks.push([proc, val, name]);
|
|
440
|
+
}
|
|
441
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
if (error.name === EmptyChannelError.name) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
else if ("channel" in proc) {
|
|
454
|
+
// If the channel read by this process was updated
|
|
455
|
+
if (checkpoint.channelVersions[proc.channel] > (seen[proc.channel] ?? 0)) {
|
|
456
|
+
// Here we don't catch EmptyChannelError because the channel
|
|
457
|
+
// must be initialized if the previous `if` condition is true
|
|
458
|
+
let val = channels[proc.channel].get();
|
|
459
|
+
if (proc.key !== undefined) {
|
|
460
|
+
val = [{ [proc.key]: val }];
|
|
461
|
+
}
|
|
462
|
+
tasks.push([proc, val, name]);
|
|
463
|
+
seen[proc.channel] = checkpoint.channelVersions[proc.channel];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return tasks;
|
|
469
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapOutput = exports.mapInput = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Map input chunk to a sequence of pending writes in the form [channel, value].
|
|
6
|
+
*/
|
|
7
|
+
function* mapInput(inputChannels,
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
chunk
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
) {
|
|
12
|
+
if (typeof inputChannels === "string") {
|
|
13
|
+
yield [inputChannels, chunk];
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
if ((chunk && typeof chunk !== "object") || Array.isArray(chunk)) {
|
|
17
|
+
throw new Error(`Expected chunk to be an object, got ${typeof chunk}`);
|
|
18
|
+
}
|
|
19
|
+
for (const k in chunk) {
|
|
20
|
+
if (inputChannels.includes(k)) {
|
|
21
|
+
yield [k, chunk[k]];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.warn(`Input channel ${k} not found in ${inputChannels}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.mapInput = mapInput;
|
|
30
|
+
/**
|
|
31
|
+
* Map pending writes (a list of [channel, value]) to output chunk.
|
|
32
|
+
*/
|
|
33
|
+
function mapOutput(outputChannels,
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
pendingWrites, channels
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
) {
|
|
38
|
+
if (typeof outputChannels === "string") {
|
|
39
|
+
if (pendingWrites.some(([chan, _]) => chan === outputChannels)) {
|
|
40
|
+
return channels[outputChannels].get();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const updated = pendingWrites
|
|
45
|
+
.filter(([chan, _]) => outputChannels.includes(chan))
|
|
46
|
+
.map(([chan, _]) => chan);
|
|
47
|
+
if (updated.length > 0) {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
return updated.reduce((acc, chan) => {
|
|
50
|
+
acc[chan] = channels[chan].get();
|
|
51
|
+
return acc;
|
|
52
|
+
}, {});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
exports.mapOutput = mapOutput;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseChannel } from "../channels/base.js";
|
|
2
|
+
/**
|
|
3
|
+
* Map input chunk to a sequence of pending writes in the form [channel, value].
|
|
4
|
+
*/
|
|
5
|
+
export declare function mapInput(inputChannels: string | Array<string>, chunk?: any): Generator<[string, any]>;
|
|
6
|
+
/**
|
|
7
|
+
* Map pending writes (a list of [channel, value]) to output chunk.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mapOutput(outputChannels: string | Array<string>, pendingWrites: Array<[string, any]>, channels: Record<string, BaseChannel>): any | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map input chunk to a sequence of pending writes in the form [channel, value].
|
|
3
|
+
*/
|
|
4
|
+
export function* mapInput(inputChannels,
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
chunk
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
) {
|
|
9
|
+
if (typeof inputChannels === "string") {
|
|
10
|
+
yield [inputChannels, chunk];
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
if ((chunk && typeof chunk !== "object") || Array.isArray(chunk)) {
|
|
14
|
+
throw new Error(`Expected chunk to be an object, got ${typeof chunk}`);
|
|
15
|
+
}
|
|
16
|
+
for (const k in chunk) {
|
|
17
|
+
if (inputChannels.includes(k)) {
|
|
18
|
+
yield [k, chunk[k]];
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.warn(`Input channel ${k} not found in ${inputChannels}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Map pending writes (a list of [channel, value]) to output chunk.
|
|
28
|
+
*/
|
|
29
|
+
export function mapOutput(outputChannels,
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
pendingWrites, channels
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
) {
|
|
34
|
+
if (typeof outputChannels === "string") {
|
|
35
|
+
if (pendingWrites.some(([chan, _]) => chan === outputChannels)) {
|
|
36
|
+
return channels[outputChannels].get();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const updated = pendingWrites
|
|
41
|
+
.filter(([chan, _]) => outputChannels.includes(chan))
|
|
42
|
+
.map(([chan, _]) => chan);
|
|
43
|
+
if (updated.length > 0) {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
return updated.reduce((acc, chan) => {
|
|
46
|
+
acc[chan] = channels[chan].get();
|
|
47
|
+
return acc;
|
|
48
|
+
}, {});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|