@nodetool-ai/workflow-runner 0.8.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/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @nodetool-ai/workflow-runner
2
+
3
+ Portable workflow runner — turns a graph + NodeRegistry into a `(Request) => Response` handler for Vercel, Cloudflare Workers, or any Web-standard runtime, for [NodeTool](https://nodetool.ai).
4
+
5
+ Wraps the kernel `WorkflowRunner` with a transport- and runtime-agnostic surface: an async-generator streaming API and a Server-Sent-Events HTTP handler built on Web-standard `Request`/`Response`/`ReadableStream`. Runs on Vercel (Node + Edge), Cloudflare Workers, Bun, and Deno. A `./browser` entry runs graphs entirely in-browser.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @nodetool-ai/workflow-runner
11
+ ```
12
+
13
+ ## Exported symbols
14
+
15
+ | Symbol | Kind | Description |
16
+ | --- | --- | --- |
17
+ | `runWorkflow` | function | Run a graph against a `NodeRegistry`, yielding live `ProcessingMessage`s |
18
+ | `RunWorkflowOptions` | interface | Options: graph, registry, params, context, storage, secrets, signal |
19
+ | `GraphData` | type | `{ nodes, edges }` graph shape |
20
+ | `createWorkflowHandler` | function | Build a `(Request) => Response` SSE handler from a registry |
21
+ | `CreateWorkflowHandlerOptions` | interface | Handler options: registry, `createContext`, `beforeRun`, platform |
22
+ | `WorkflowRequestBody` | interface | Parsed request body (`graph`, `params`, `workflow_id`, `job_id`) |
23
+ | `envSecretResolver` | function | Secret resolver backed by environment variables |
24
+ | `createBrowserRegistry` | function | Build a `NodeRegistry` of browser-runnable nodes (`./browser`) |
25
+ | `runBrowserWorkflow` | function | Run a graph in the browser (`./browser`) |
26
+ | `graphRunsInRegistry` | function | Check whether a graph's nodes are all present in a registry (`./browser`) |
27
+
28
+ ## Usage
29
+
30
+ ```ts
31
+ import { createWorkflowHandler, envSecretResolver } from "@nodetool-ai/workflow-runner";
32
+ import { NodeRegistry } from "@nodetool-ai/node-sdk";
33
+
34
+ const registry = new NodeRegistry();
35
+ // ...register the nodes available in this deployment
36
+
37
+ // A Web-standard fetch handler — export it from a Vercel / Cloudflare route
38
+ export const POST = createWorkflowHandler({
39
+ registry,
40
+ createContext: () => ({ secretResolver: envSecretResolver }) as never
41
+ });
42
+ ```
43
+
44
+ Or drive the run directly and stream messages:
45
+
46
+ ```ts
47
+ import { runWorkflow } from "@nodetool-ai/workflow-runner";
48
+
49
+ for await (const message of runWorkflow({ graph, registry, params })) {
50
+ console.log(message.type, message);
51
+ }
52
+ ```
53
+
54
+ ## Links
55
+
56
+ - [NodeTool](https://nodetool.ai)
57
+ - [GitHub](https://github.com/nodetool-ai/nodetool)
@@ -0,0 +1 @@
1
+ 2026-09-28T09:19:17.031Z
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Browser-side workflow execution.
3
+ *
4
+ * Thin, reusable helpers that let a Web client (React app, Electron renderer,
5
+ * React Native WebView, edge isolate, …) execute a sub-graph of **pure browser
6
+ * nodes** locally — no server round-trip — while emitting the *exact same*
7
+ * `ProcessingMessage` stream the unified WebSocket server produces.
8
+ *
9
+ * The kernel `WorkflowRunner` and `ProcessingContext` are already runtime- and
10
+ * transport-agnostic (no static `node:fs`/`child_process` deps; node built-ins
11
+ * load lazily and degrade in the browser). What was missing was a small façade
12
+ * that (1) builds a `NodeRegistry` scoped to the `"browser"` platform and
13
+ * (2) stamps every emitted message with `job_id`/`workflow_id` so a consumer
14
+ * can route browser-produced messages through the very same pipeline it uses
15
+ * for server messages.
16
+ *
17
+ * The node classes are injected by the caller (keeping this package agnostic of
18
+ * any concrete node set) — pass `ALL_BROWSER_NODES` from
19
+ * `@nodetool-ai/base-nodes/platforms/browser`, or any curated `NodeClass[]`.
20
+ */
21
+ import type { RunResult } from "@nodetool-ai/kernel";
22
+ import { NodeRegistry, type NodeClass } from "@nodetool-ai/node-sdk";
23
+ import type { Platform, ProcessingMessage } from "@nodetool-ai/protocol";
24
+ import { type GraphData, type RunWorkflowOptions } from "./run.js";
25
+ export type { GraphData };
26
+ /**
27
+ * Build a `NodeRegistry` containing only the classes that declare support for
28
+ * the `"browser"` platform. Classes that don't support the browser are dropped
29
+ * (defence in depth — the curated browser set should already be browser-safe).
30
+ */
31
+ export declare function createBrowserRegistry(nodeClasses: readonly NodeClass[], platform?: Platform): NodeRegistry;
32
+ export declare function graphRunsInRegistry(graph: GraphData, registry: Pick<NodeRegistry, "has">): boolean;
33
+ export interface RunBrowserWorkflowOptions extends Omit<RunWorkflowOptions, "platform"> {
34
+ /**
35
+ * Platform to validate the graph against. Defaults to `"browser"` so a graph
36
+ * containing a server-only node fails fast (before any actor spawns) instead
37
+ * of throwing deep inside a node's `process()`.
38
+ */
39
+ platform?: Platform;
40
+ }
41
+ /**
42
+ * Execute a browser graph and yield `ProcessingMessage`s shaped like the
43
+ * unified WebSocket server's stream: every message carries `job_id` and
44
+ * `workflow_id`, so a client can route them through the same handler it uses
45
+ * for server-sent messages. The final `RunResult` is the generator's return
46
+ * value.
47
+ *
48
+ * ```ts
49
+ * const registry = createBrowserRegistry(ALL_BROWSER_NODES);
50
+ * const gen = runBrowserWorkflow({ graph, registry, workflowId, jobId });
51
+ * for (let r = await gen.next(); !r.done; r = await gen.next()) {
52
+ * deliver(r.value); // a ProcessingMessage, already stamped
53
+ * }
54
+ * const result = (await gen.next()).value; // RunResult on `done`
55
+ * ```
56
+ */
57
+ export declare function runBrowserWorkflow(opts: RunBrowserWorkflowOptions): AsyncGenerator<ProcessingMessage, RunResult, void>;
58
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEhF,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,SAAS,SAAS,EAAE,EACjC,QAAQ,GAAE,QAAoB,GAC7B,YAAY,CAOd;AAeD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,GAClC,OAAO,CAIT;AAED,MAAM,WAAW,yBACf,SAAQ,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAuB,kBAAkB,CACvC,IAAI,EAAE,yBAAyB,GAC9B,cAAc,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,CAiBpD"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Browser-side workflow execution.
3
+ *
4
+ * Thin, reusable helpers that let a Web client (React app, Electron renderer,
5
+ * React Native WebView, edge isolate, …) execute a sub-graph of **pure browser
6
+ * nodes** locally — no server round-trip — while emitting the *exact same*
7
+ * `ProcessingMessage` stream the unified WebSocket server produces.
8
+ *
9
+ * The kernel `WorkflowRunner` and `ProcessingContext` are already runtime- and
10
+ * transport-agnostic (no static `node:fs`/`child_process` deps; node built-ins
11
+ * load lazily and degrade in the browser). What was missing was a small façade
12
+ * that (1) builds a `NodeRegistry` scoped to the `"browser"` platform and
13
+ * (2) stamps every emitted message with `job_id`/`workflow_id` so a consumer
14
+ * can route browser-produced messages through the very same pipeline it uses
15
+ * for server messages.
16
+ *
17
+ * The node classes are injected by the caller (keeping this package agnostic of
18
+ * any concrete node set) — pass `ALL_BROWSER_NODES` from
19
+ * `@nodetool-ai/base-nodes/platforms/browser`, or any curated `NodeClass[]`.
20
+ */
21
+ import { NodeRegistry } from "@nodetool-ai/node-sdk";
22
+ import { runWorkflow } from "./run.js";
23
+ /**
24
+ * Build a `NodeRegistry` containing only the classes that declare support for
25
+ * the `"browser"` platform. Classes that don't support the browser are dropped
26
+ * (defence in depth — the curated browser set should already be browser-safe).
27
+ */
28
+ export function createBrowserRegistry(nodeClasses, platform = "browser") {
29
+ const registry = new NodeRegistry();
30
+ for (const nodeClass of nodeClasses) {
31
+ if (!nodeClass?.nodeType)
32
+ continue;
33
+ registry.register(nodeClass);
34
+ }
35
+ return registry.forPlatform(platform);
36
+ }
37
+ /**
38
+ * True iff every node in `graph` resolves to a class registered in `registry`.
39
+ * A graph that passes can run entirely client-side; one that doesn't must be
40
+ * sent to the server (it references nodes the browser can't execute).
41
+ */
42
+ /**
43
+ * Whether a node from an untrusted graph actually names a type. Generic in the
44
+ * caller's type so it narrows the declared `string` and a wire value alike.
45
+ */
46
+ function isNodeType(value) {
47
+ return typeof value === "string" && value !== "";
48
+ }
49
+ export function graphRunsInRegistry(graph, registry) {
50
+ const nodes = graph?.nodes ?? [];
51
+ if (nodes.length === 0)
52
+ return false;
53
+ return nodes.every((node) => isNodeType(node.type) && registry.has(node.type));
54
+ }
55
+ /**
56
+ * Execute a browser graph and yield `ProcessingMessage`s shaped like the
57
+ * unified WebSocket server's stream: every message carries `job_id` and
58
+ * `workflow_id`, so a client can route them through the same handler it uses
59
+ * for server-sent messages. The final `RunResult` is the generator's return
60
+ * value.
61
+ *
62
+ * ```ts
63
+ * const registry = createBrowserRegistry(ALL_BROWSER_NODES);
64
+ * const gen = runBrowserWorkflow({ graph, registry, workflowId, jobId });
65
+ * for (let r = await gen.next(); !r.done; r = await gen.next()) {
66
+ * deliver(r.value); // a ProcessingMessage, already stamped
67
+ * }
68
+ * const result = (await gen.next()).value; // RunResult on `done`
69
+ * ```
70
+ */
71
+ export async function* runBrowserWorkflow(opts) {
72
+ const jobId = opts.jobId ?? generateJobId();
73
+ const workflowId = opts.workflowId ?? null;
74
+ const gen = runWorkflow({
75
+ ...opts,
76
+ jobId,
77
+ platform: opts.platform ?? "browser"
78
+ });
79
+ while (true) {
80
+ const next = await gen.next();
81
+ if (next.done) {
82
+ return stampResult(next.value, jobId, workflowId);
83
+ }
84
+ yield stamp(next.value, jobId, workflowId);
85
+ }
86
+ }
87
+ function stamp(message, jobId, workflowId) {
88
+ // SAFETY: `job_id`/`workflow_id` are routing fields the transport stamps on
89
+ // every message, but a few union variants (`save_update`, for one) do not
90
+ // declare them — so both the read and the stamped result go through the
91
+ // routing shape instead of re-declaring the fields on each variant.
92
+ const routing = message;
93
+ // SAFETY: the spread preserves `message`'s own discriminant and fields, so
94
+ // the result is the same variant it went in as, plus the two routing fields.
95
+ return {
96
+ ...message,
97
+ job_id: routing.job_id ?? jobId,
98
+ workflow_id: routing.workflow_id ?? workflowId
99
+ };
100
+ }
101
+ function stampResult(result, jobId, workflowId) {
102
+ return {
103
+ ...result,
104
+ messages: result.messages.map((m) => stamp(m, jobId, workflowId))
105
+ };
106
+ }
107
+ function generateJobId() {
108
+ // SAFETY: `crypto.randomUUID` is absent from older browsers and from
109
+ // non-secure contexts, and the DOM lib types it as always present — so the
110
+ // optional shape here is what the guard below actually checks.
111
+ const g = globalThis;
112
+ if (g.crypto?.randomUUID)
113
+ return g.crypto.randomUUID();
114
+ return `job_${Date.now().toString(36)}_${Math.random()
115
+ .toString(36)
116
+ .slice(2, 10)}`;
117
+ }
118
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * envSecretResolver — adapt an env-vars dictionary to ProcessingContext's
3
+ * `secretResolver` shape. Use this when there is no OS keychain (serverless,
4
+ * Workers) and secrets arrive via platform-managed environment variables.
5
+ */
6
+ export declare function envSecretResolver(env: Record<string, string | undefined>): (key: string) => string | null;
7
+ //# sourceMappingURL=env-secret-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-secret-resolver.d.ts","sourceRoot":"","sources":["../src/env-secret-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAEhC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * envSecretResolver — adapt an env-vars dictionary to ProcessingContext's
3
+ * `secretResolver` shape. Use this when there is no OS keychain (serverless,
4
+ * Workers) and secrets arrive via platform-managed environment variables.
5
+ */
6
+ export function envSecretResolver(env) {
7
+ return (key) => env[key] ?? null;
8
+ }
9
+ //# sourceMappingURL=env-secret-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-secret-resolver.js","sourceRoot":"","sources":["../src/env-secret-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAuC;IAEvC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AACnC,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * createWorkflowHandler — a (Request) => Response handler that runs a workflow
3
+ * and streams messages as Server-Sent Events.
4
+ *
5
+ * Works on Vercel (Node + Edge), Cloudflare Workers, Bun, Deno — anywhere
6
+ * Web standard `Request`/`Response`/`ReadableStream` are available.
7
+ *
8
+ * Request body (JSON):
9
+ * {
10
+ * "graph": { "nodes": [...], "edges": [...] },
11
+ * "params": { ... }, // optional, keyed by input-node name
12
+ * "workflow_id": "...", // optional
13
+ * "job_id": "..." // optional
14
+ * }
15
+ *
16
+ * Response: text/event-stream
17
+ * data: { ...ProcessingMessage }
18
+ * ...
19
+ * event: result
20
+ * data: { ...RunResult }
21
+ *
22
+ * Note: request parse/validation errors are returned as JSON (4xx). Once
23
+ * the SSE Response is created, workflow failures are reported as an SSE
24
+ * `error` event (HTTP 200).
25
+ */
26
+ import type { Platform, ProcessingMessage } from "@nodetool-ai/protocol";
27
+ import type { NodeRegistry } from "@nodetool-ai/node-sdk";
28
+ import { ProcessingContext } from "@nodetool-ai/runtime/context";
29
+ import { type GraphData, type RunWorkflowOptions } from "./run.js";
30
+ export interface CreateWorkflowHandlerOptions {
31
+ /** NodeRegistry pre-populated with the nodes available in this deployment. */
32
+ registry: NodeRegistry;
33
+ /**
34
+ * Optional per-request context factory. Receives the incoming Request so
35
+ * the caller can wire env-specific bindings (R2/S3 client, secrets, etc.).
36
+ * Returning `void` falls back to a minimal in-memory context.
37
+ */
38
+ createContext?: (req: Request) => ProcessingContext | Promise<ProcessingContext> | void | Promise<void>;
39
+ /**
40
+ * Optional hook to transform / authorize the parsed request body before
41
+ * execution. Throw to abort with a 4xx; return a body to use it.
42
+ */
43
+ beforeRun?: (body: WorkflowRequestBody, req: Request) => WorkflowRequestBody | Promise<WorkflowRequestBody>;
44
+ /**
45
+ * Deployment platform this handler serves. When set, the runner rejects
46
+ * graphs containing nodes that do not declare support for this platform.
47
+ * The registry passed in should normally have been filtered with
48
+ * `registry.forPlatform(platform)` for matching bundle / runtime semantics.
49
+ */
50
+ platform?: Platform;
51
+ }
52
+ export interface WorkflowRequestBody {
53
+ graph: GraphData;
54
+ /** The run's params, exactly as `runWorkflow` takes them. */
55
+ params?: RunWorkflowOptions["params"];
56
+ workflow_id?: string;
57
+ job_id?: string;
58
+ }
59
+ export declare function createWorkflowHandler(opts: CreateWorkflowHandlerOptions): (req: Request) => Promise<Response>;
60
+ export type { ProcessingMessage };
61
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEhF,MAAM,WAAW,4BAA4B;IAC3C,8EAA8E;IAC9E,QAAQ,EAAE,YAAY,CAAC;IAEvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,OAAO,KAEV,iBAAiB,GACjB,OAAO,CAAC,iBAAiB,CAAC,GAC1B,IAAI,GACJ,OAAO,CAAC,IAAI,CAAC,CAAC;IAElB;;;OAGG;IACH,SAAS,CAAC,EAAE,CACV,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,OAAO,KACT,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAExD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,4BAA4B,GACjC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA+ErC;AAoCD,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * createWorkflowHandler — a (Request) => Response handler that runs a workflow
3
+ * and streams messages as Server-Sent Events.
4
+ *
5
+ * Works on Vercel (Node + Edge), Cloudflare Workers, Bun, Deno — anywhere
6
+ * Web standard `Request`/`Response`/`ReadableStream` are available.
7
+ *
8
+ * Request body (JSON):
9
+ * {
10
+ * "graph": { "nodes": [...], "edges": [...] },
11
+ * "params": { ... }, // optional, keyed by input-node name
12
+ * "workflow_id": "...", // optional
13
+ * "job_id": "..." // optional
14
+ * }
15
+ *
16
+ * Response: text/event-stream
17
+ * data: { ...ProcessingMessage }
18
+ * ...
19
+ * event: result
20
+ * data: { ...RunResult }
21
+ *
22
+ * Note: request parse/validation errors are returned as JSON (4xx). Once
23
+ * the SSE Response is created, workflow failures are reported as an SSE
24
+ * `error` event (HTTP 200).
25
+ */
26
+ import { ProcessingContext } from "@nodetool-ai/runtime/context";
27
+ import { runWorkflow } from "./run.js";
28
+ export function createWorkflowHandler(opts) {
29
+ return async function handle(req) {
30
+ if (req.method !== "POST") {
31
+ return jsonError(405, "method_not_allowed");
32
+ }
33
+ let body;
34
+ try {
35
+ // SAFETY: an untrusted HTTP body. Nothing is read from it before the
36
+ // object/`graph` check below, and the graph itself is validated against
37
+ // the node registry by `runWorkflow`, which rejects an unknown node type.
38
+ body = (await req.json());
39
+ }
40
+ catch {
41
+ return jsonError(400, "invalid_json");
42
+ }
43
+ if (!isObjectBody(body) || !body.graph) {
44
+ return jsonError(400, "missing_graph");
45
+ }
46
+ try {
47
+ if (opts.beforeRun)
48
+ body = await opts.beforeRun(body, req);
49
+ }
50
+ catch {
51
+ return jsonError(400, "before_run_failed");
52
+ }
53
+ const context = (await opts.createContext?.(req)) ?? undefined;
54
+ const runOpts = {
55
+ graph: body.graph,
56
+ registry: opts.registry,
57
+ params: body.params,
58
+ workflowId: body.workflow_id,
59
+ jobId: body.job_id,
60
+ context: context instanceof ProcessingContext ? context : undefined,
61
+ signal: req.signal,
62
+ platform: opts.platform
63
+ };
64
+ const stream = new ReadableStream({
65
+ async start(controller) {
66
+ const enc = new TextEncoder();
67
+ const write = (chunk) => {
68
+ controller.enqueue(enc.encode(chunk));
69
+ };
70
+ try {
71
+ const gen = runWorkflow(runOpts);
72
+ while (true) {
73
+ const { value, done } = await gen.next();
74
+ if (done) {
75
+ write(formatSse("result", value));
76
+ break;
77
+ }
78
+ write(formatSse(null, value));
79
+ }
80
+ }
81
+ catch {
82
+ write(formatSse("error", {
83
+ message: "Workflow execution failed"
84
+ }));
85
+ }
86
+ finally {
87
+ controller.close();
88
+ }
89
+ },
90
+ cancel() {
91
+ // Request aborted — runWorkflow honours opts.signal.
92
+ }
93
+ });
94
+ return new Response(stream, {
95
+ status: 200,
96
+ headers: {
97
+ "content-type": "text/event-stream; charset=utf-8",
98
+ "cache-control": "no-cache, no-transform",
99
+ "x-accel-buffering": "no",
100
+ connection: "keep-alive"
101
+ }
102
+ });
103
+ };
104
+ }
105
+ /** Whether a decoded JSON body arrived as an object at all. */
106
+ function isObjectBody(body) {
107
+ return typeof body === "object" && body !== null;
108
+ }
109
+ function formatSse(event, data) {
110
+ const payload = JSON.stringify(data);
111
+ const prefix = event ? `event: ${event}\n` : "";
112
+ return `${prefix}data: ${payload}\n\n`;
113
+ }
114
+ const ERROR_MESSAGES = new Map([
115
+ ["method_not_allowed", "POST required"],
116
+ ["invalid_json", "Body must be valid JSON"],
117
+ ["missing_graph", "Body must include a `graph`"],
118
+ ["before_run_failed", "beforeRun hook failed"]
119
+ ]);
120
+ function jsonError(status, code) {
121
+ return new Response(JSON.stringify({
122
+ error: code,
123
+ message: ERROR_MESSAGES.get(code) ?? "Request failed"
124
+ }), {
125
+ status,
126
+ headers: { "content-type": "application/json" }
127
+ });
128
+ }
129
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @nodetool-ai/workflow-runner – Public API
3
+ *
4
+ * Portable workflow runner for serverless / edge deployments. Wraps the
5
+ * kernel's WorkflowRunner with a (Request) => Response handler and an
6
+ * AsyncGenerator-shaped streaming API. Transport-agnostic and runtime-agnostic
7
+ * — works on Vercel (Node + Edge), Cloudflare Workers, Bun, and Deno.
8
+ */
9
+ export { runWorkflow, type GraphData, type RunWorkflowOptions } from "./run.js";
10
+ export { createWorkflowHandler, type CreateWorkflowHandlerOptions, type WorkflowRequestBody } from "./handler.js";
11
+ export { envSecretResolver } from "./env-secret-resolver.js";
12
+ export { createBrowserRegistry, graphRunsInRegistry, runBrowserWorkflow, type RunBrowserWorkflowOptions } from "./browser.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,yBAAyB,EAC/B,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @nodetool-ai/workflow-runner – Public API
3
+ *
4
+ * Portable workflow runner for serverless / edge deployments. Wraps the
5
+ * kernel's WorkflowRunner with a (Request) => Response handler and an
6
+ * AsyncGenerator-shaped streaming API. Transport-agnostic and runtime-agnostic
7
+ * — works on Vercel (Node + Edge), Cloudflare Workers, Bun, and Deno.
8
+ */
9
+ export { runWorkflow } from "./run.js";
10
+ export { createWorkflowHandler } from "./handler.js";
11
+ export { envSecretResolver } from "./env-secret-resolver.js";
12
+ export { createBrowserRegistry, graphRunsInRegistry, runBrowserWorkflow } from "./browser.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAA2C,MAAM,UAAU,CAAC;AAChF,OAAO,EACL,qBAAqB,EAGtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,cAAc,CAAC"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * runWorkflow — execute a graph against a NodeRegistry and yield messages live.
3
+ *
4
+ * This is the portable core: no fs, no subprocess, no transport assumptions.
5
+ * The caller supplies the graph, the registry, and (optionally) a context.
6
+ * Everything platform-specific (storage backend, secret resolution, etc.)
7
+ * is injected via the context.
8
+ */
9
+ import { WorkflowRunner, type RunJobRequest, type RunResult } from "@nodetool-ai/kernel";
10
+ import { type NodeRegistry } from "@nodetool-ai/node-sdk";
11
+ import { ProcessingContext, type CacheAdapter, type SandboxModuleCatalog, type StorageAdapter } from "@nodetool-ai/runtime/context";
12
+ import type { Edge, NodeDescriptor, Platform, ProcessingMessage } from "@nodetool-ai/protocol";
13
+ export type GraphData = {
14
+ nodes: NodeDescriptor[];
15
+ edges: Edge[];
16
+ };
17
+ export interface RunWorkflowOptions {
18
+ graph: GraphData;
19
+ registry: NodeRegistry;
20
+ /** The run's params, exactly as the job request carries them. */
21
+ params?: RunJobRequest["params"];
22
+ jobId?: string;
23
+ workflowId?: string;
24
+ /** Pre-built context. If omitted, a minimal one is created from the fields below. */
25
+ context?: ProcessingContext;
26
+ /** Optional context inputs (ignored when `context` is provided). */
27
+ storage?: StorageAdapter | null;
28
+ workspaceStorage?: StorageAdapter | null;
29
+ cache?: CacheAdapter;
30
+ environment?: Record<string, string>;
31
+ secretResolver?: (key: string, userId: string) => Promise<string | null | undefined> | string | null | undefined;
32
+ /**
33
+ * Catalog the run's Code nodes resolve their imports through. A host with no
34
+ * process-wide catalog — every browser — passes one here or the imports
35
+ * cannot be served.
36
+ */
37
+ sandboxModuleCatalog?: SandboxModuleCatalog | null;
38
+ /** Abort the run when this signal fires. */
39
+ signal?: AbortSignal;
40
+ /**
41
+ * Receives the live WorkflowRunner right after construction — lets the
42
+ * caller push live property updates into running nodes
43
+ * (`runner.updateNodeProperties`) or otherwise drive the run.
44
+ */
45
+ onRunner?: (runner: WorkflowRunner) => void;
46
+ /**
47
+ * Reject graphs whose nodes do not declare support for this deployment
48
+ * platform. When set, the runner runs both property validation and
49
+ * platform validation as a pre-flight; unsupported nodes cause the run
50
+ * to fail before any actor is spawned.
51
+ */
52
+ platform?: Platform;
53
+ }
54
+ export declare function runWorkflow(opts: RunWorkflowOptions): AsyncGenerator<ProcessingMessage, RunResult, void>;
55
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EAEd,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,IAAI,EACJ,cAAc,EACd,QAAQ,EACR,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,iEAAiE;IACjE,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qFAAqF;IACrF,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAE5B,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAEpE;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAEnD,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAE5C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAsCD,wBAAuB,WAAW,CAChC,IAAI,EAAE,kBAAkB,GACvB,cAAc,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,CAwGpD"}
package/dist/run.js ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * runWorkflow — execute a graph against a NodeRegistry and yield messages live.
3
+ *
4
+ * This is the portable core: no fs, no subprocess, no transport assumptions.
5
+ * The caller supplies the graph, the registry, and (optionally) a context.
6
+ * Everything platform-specific (storage backend, secret resolution, etc.)
7
+ * is injected via the context.
8
+ */
9
+ import { WorkflowRunner } from "@nodetool-ai/kernel";
10
+ import { hydrateGraphNodeFlags } from "@nodetool-ai/node-sdk";
11
+ // Narrow `/context` subpath (not the package root) so the browser bundle of
12
+ // this runner doesn't pull the provider / python-bridge barrel.
13
+ import { ProcessingContext } from "@nodetool-ai/runtime/context";
14
+ /**
15
+ * Execute a workflow and stream ProcessingMessages as they are emitted.
16
+ *
17
+ * Yields each message live; the final `RunResult` is the generator's return value.
18
+ *
19
+ * ```ts
20
+ * for await (const msg of runWorkflow(opts)) { ... }
21
+ * ```
22
+ */
23
+ /**
24
+ * The context the run builds when the caller supplied none. The sandbox module
25
+ * catalog is left out entirely when there is none, rather than set to
26
+ * `undefined`.
27
+ */
28
+ function contextInit(opts, jobId) {
29
+ const init = {
30
+ jobId,
31
+ workflowId: opts.workflowId ?? null,
32
+ storage: opts.storage ?? null,
33
+ workspaceStorage: opts.workspaceStorage ?? null,
34
+ cache: opts.cache,
35
+ environment: opts.environment,
36
+ secretResolver: opts.secretResolver,
37
+ // The caller streams via a message listener and never drains the pull
38
+ // queue — retaining it would pin every streamed chunk.
39
+ retainMessageQueue: false
40
+ };
41
+ if (opts.sandboxModuleCatalog !== undefined) {
42
+ init.sandboxModuleCatalog = opts.sandboxModuleCatalog;
43
+ }
44
+ return init;
45
+ }
46
+ export async function* runWorkflow(opts) {
47
+ const jobId = opts.jobId ?? generateJobId();
48
+ if (opts.signal?.aborted) {
49
+ const message = {
50
+ type: "job_update",
51
+ status: "cancelled",
52
+ job_id: jobId,
53
+ workflow_id: opts.workflowId ?? null
54
+ };
55
+ return { outputs: {}, messages: [message], status: "cancelled" };
56
+ }
57
+ const graph = hydrateGraphNodeFlags(opts.graph, opts.registry);
58
+ const context = opts.context ?? new ProcessingContext(contextInit(opts, jobId));
59
+ const runner = new WorkflowRunner(jobId, {
60
+ resolveExecutor: (node) => opts.registry.resolve(node),
61
+ executionContext: context,
62
+ validateNode: composeValidators(opts.registry.createNodeValidator(), opts.platform
63
+ ? opts.registry.createPlatformValidator(opts.platform)
64
+ : null)
65
+ });
66
+ opts.onRunner?.(runner);
67
+ const queue = [];
68
+ let wake = null;
69
+ const wakeQueue = () => {
70
+ const w = wake;
71
+ wake = null;
72
+ w?.();
73
+ };
74
+ const unsubscribe = context.addMessageListener((message) => {
75
+ queue.push(message);
76
+ wakeQueue();
77
+ });
78
+ const request = {
79
+ job_id: jobId,
80
+ workflow_id: opts.workflowId,
81
+ params: opts.params
82
+ };
83
+ let finished = false;
84
+ let runError;
85
+ let result = null;
86
+ const runPromise = runner
87
+ .run(request, graph)
88
+ .then((r) => {
89
+ result = r;
90
+ })
91
+ .catch((err) => {
92
+ runError = err;
93
+ })
94
+ .finally(() => {
95
+ finished = true;
96
+ wakeQueue();
97
+ });
98
+ let cancelRequested = false;
99
+ const cancelRun = () => {
100
+ if (cancelRequested) {
101
+ wakeQueue();
102
+ return;
103
+ }
104
+ cancelRequested = true;
105
+ if (!finished) {
106
+ runner.cancel();
107
+ }
108
+ wakeQueue();
109
+ };
110
+ opts.signal?.addEventListener("abort", cancelRun, { once: true });
111
+ try {
112
+ while (!finished || queue.length > 0) {
113
+ if (queue.length === 0) {
114
+ if (opts.signal?.aborted) {
115
+ cancelRun();
116
+ break;
117
+ }
118
+ await new Promise((resolve) => {
119
+ wake = resolve;
120
+ });
121
+ continue;
122
+ }
123
+ const msg = queue.shift();
124
+ yield msg;
125
+ }
126
+ await runPromise;
127
+ }
128
+ finally {
129
+ opts.signal?.removeEventListener("abort", cancelRun);
130
+ unsubscribe();
131
+ }
132
+ if (runError)
133
+ throw runError;
134
+ if (!result) {
135
+ throw new Error("Workflow runner exited without a result");
136
+ }
137
+ return result;
138
+ }
139
+ function composeValidators(a, b) {
140
+ if (!b)
141
+ return a;
142
+ return (descriptor, connectedHandles) => {
143
+ const aIssues = a(descriptor, connectedHandles) ?? [];
144
+ const bIssues = b(descriptor, connectedHandles) ?? [];
145
+ if (aIssues.length === 0)
146
+ return bIssues;
147
+ if (bIssues.length === 0)
148
+ return aIssues;
149
+ return [...aIssues, ...bIssues];
150
+ };
151
+ }
152
+ function generateJobId() {
153
+ const g = globalThis;
154
+ if (g.crypto?.randomUUID)
155
+ return g.crypto.randomUUID();
156
+ return `job_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
157
+ }
158
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@nodetool-ai/workflow-runner",
3
+ "version": "0.8.1",
4
+ "description": "Portable workflow runner — turns a graph + NodeRegistry into a (Request) => Response handler for Vercel, Cloudflare Workers, or any Web-standard runtime",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.0.0"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "node ../../scripts/build-typescript-workspace.mjs",
13
+ "test": "node ../../scripts/run-vitest.mjs run",
14
+ "test:watch": "node ../../scripts/run-vitest.mjs",
15
+ "test:e2e": "playwright test --config e2e/playwright.config.ts",
16
+ "typecheck:e2e": "node ../../scripts/run-tsc.mjs --noEmit -p e2e/tsconfig.json",
17
+ "lint": "node ../../scripts/run-tsc.mjs --noEmit"
18
+ },
19
+ "dependencies": {
20
+ "@nodetool-ai/kernel": "^0.8.1",
21
+ "@nodetool-ai/node-sdk": "^0.8.1",
22
+ "@nodetool-ai/protocol": "^0.8.1",
23
+ "@nodetool-ai/runtime": "^0.8.1"
24
+ },
25
+ "devDependencies": {
26
+ "@nodetool-ai/agents": "*",
27
+ "@nodetool-ai/base-nodes": "*",
28
+ "@nodetool-ai/code-nodes": "*",
29
+ "@nodetool-ai/gpu": "*",
30
+ "@playwright/test": "^1.50.0",
31
+ "@types/node": "^22.0.0",
32
+ "@webgpu/types": "^0.1.70",
33
+ "typescript": "npm:@typescript/typescript6@6.0.2",
34
+ "vite": "^6.0.0",
35
+ "vite-plugin-node-polyfills": "^0.28.0",
36
+ "vitest": "^5.0.1"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "nodetool-dev": "./src/index.ts",
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.js",
43
+ "default": "./dist/index.js"
44
+ },
45
+ "./browser": {
46
+ "nodetool-dev": "./src/browser.ts",
47
+ "types": "./dist/browser.d.ts",
48
+ "import": "./dist/browser.js",
49
+ "default": "./dist/browser.js"
50
+ }
51
+ },
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/nodetool-ai/nodetool.git",
56
+ "directory": "packages/workflow-runner"
57
+ },
58
+ "homepage": "https://nodetool.ai",
59
+ "bugs": {
60
+ "url": "https://github.com/nodetool-ai/nodetool/issues"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "files": [
66
+ "dist",
67
+ "README.md"
68
+ ],
69
+ "keywords": [
70
+ "nodetool",
71
+ "ai",
72
+ "workflow",
73
+ "workflow-automation",
74
+ "typescript",
75
+ "workflow-runner",
76
+ "serverless",
77
+ "vercel",
78
+ "cloudflare-workers",
79
+ "edge",
80
+ "web-standard"
81
+ ]
82
+ }