@nodetool-ai/code-nodes 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 +25 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/code-node.d.ts +218 -0
- package/dist/nodes/code-node.d.ts.map +1 -0
- package/dist/nodes/code-node.js +1010 -0
- package/dist/nodes/code-node.js.map +1 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @nodetool-ai/code-nodes
|
|
2
|
+
|
|
3
|
+
Code-execution nodes for [NodeTool](https://nodetool.ai).
|
|
4
|
+
|
|
5
|
+
Run JavaScript inside visual AI workflows in a QuickJS WASM sandbox.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @nodetool-ai/code-nodes
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Nodes
|
|
14
|
+
|
|
15
|
+
**Code execution** — `nodetool.code.Code` runs JavaScript in a QuickJS WASM
|
|
16
|
+
sandbox with `fetch()`, workspace file access, secrets, and media helpers.
|
|
17
|
+
Dynamic inputs arrive on the `inputs` object; the returned object becomes the
|
|
18
|
+
outputs. Libraries are sandbox packs the body imports — the node declares no
|
|
19
|
+
packages; the host resolves each specifier against the installed catalog. Full
|
|
20
|
+
reference: [JavaScript Sandbox](https://docs.nodetool.ai/javascript-sandbox).
|
|
21
|
+
|
|
22
|
+
## Links
|
|
23
|
+
|
|
24
|
+
- [NodeTool](https://nodetool.ai)
|
|
25
|
+
- [GitHub](https://github.com/nodetool-ai/nodetool)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2026-09-28T09:19:16.401Z
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Code Node — sandboxed JavaScript execution via QuickJS WASM.
|
|
3
|
+
*
|
|
4
|
+
* Runs user code in an isolated QuickJS WebAssembly guest (see
|
|
5
|
+
* `@nodetool-ai/agents/js-sandbox`) with standard JavaScript plus the bridge
|
|
6
|
+
* APIs: fetch(), workspace (text/bytes/stat/mkdir/remove/copy/move/root),
|
|
7
|
+
* getSecret(), sleep(), progress(), crypto (randomUUID/digest/hmac), format,
|
|
8
|
+
* media (read a media input's bytes, build a media output) and the base64/hex
|
|
9
|
+
* helpers. Dynamic inputs arrive on the `inputs` object.
|
|
10
|
+
*
|
|
11
|
+
* On a server host the code also gets the `nodetool` object model — the
|
|
12
|
+
* platform as objects (`nodetool.workflows`, `nodetool.assets`, …), backed by
|
|
13
|
+
* the agent toolbelt through imported capability modules. The belt is loaded
|
|
14
|
+
* lazily and only on Node: the in-browser runner bundles this module, so the
|
|
15
|
+
* toolbelt (native canvas, IMAP, execution) must never sit in its static
|
|
16
|
+
* import graph. Without a belt, `nodetool.capabilities()` reports `{}` and
|
|
17
|
+
* every method throws naming its missing tool instead of a ReferenceError.
|
|
18
|
+
*
|
|
19
|
+
* Outputs leave the body through two host calls: `await emit(name, value)`
|
|
20
|
+
* streams one value downstream immediately, `await output(name, value)` sets a
|
|
21
|
+
* handle's final value, delivered as one bag when the body completes. The
|
|
22
|
+
* body's return value carries no outputs.
|
|
23
|
+
*
|
|
24
|
+
* Example:
|
|
25
|
+
* // inputs: { x: 5, text: "hello" }
|
|
26
|
+
* // code:
|
|
27
|
+
* for (const word of inputs.text.split(" ")) await emit("word", word);
|
|
28
|
+
* await output("sum", inputs.x + 10);
|
|
29
|
+
* // streams { word: … } per word, then the final bag { sum: 15 }
|
|
30
|
+
*
|
|
31
|
+
* A body that calls neither runs the deprecated return/yield contract.
|
|
32
|
+
*
|
|
33
|
+
* Inputs arrive one of two ways. A body that never mentions `stream` runs once
|
|
34
|
+
* per incoming item with `inputs` holding that item's snapshot. A body that
|
|
35
|
+
* calls `stream` runs once for the whole stream and pulls values itself
|
|
36
|
+
* (`stream(name)`, `stream.any()`, `stream.first`, `stream.open`), executing
|
|
37
|
+
* through {@link CodeNode.run} — the kernel's streaming-input mode, which
|
|
38
|
+
* hydration selects per node from the body itself.
|
|
39
|
+
*/
|
|
40
|
+
import { BaseNode, type JsScriptLink } from "@nodetool-ai/node-sdk";
|
|
41
|
+
import type { ProcessingContext, StreamingInputs, StreamingOutputs } from "@nodetool-ai/runtime";
|
|
42
|
+
import { type RunSandboxResult } from "@nodetool-ai/agents/js-sandbox";
|
|
43
|
+
/** This node's output bag is the sandbox's: handle name → the guest's value. */
|
|
44
|
+
type CodeOutputs = NonNullable<RunSandboxResult["outputs"]>;
|
|
45
|
+
/** Type-only view of the agents package index; erased at compile time. */
|
|
46
|
+
type AgentsModule = typeof import("@nodetool-ai/agents");
|
|
47
|
+
type AgentTool = InstanceType<AgentsModule["Tool"]>;
|
|
48
|
+
/**
|
|
49
|
+
* Hand the Code node the agents package a host already holds.
|
|
50
|
+
*
|
|
51
|
+
* The bare specifier below resolves in a workspace checkout and in the CLI,
|
|
52
|
+
* and resolves nowhere in the bundled backend: `bundle-backend.mjs` inlines
|
|
53
|
+
* every workspace package into `server.mjs` and stages only third-party
|
|
54
|
+
* externals, so `node_modules/@nodetool-ai/agents` does not exist in the
|
|
55
|
+
* packaged desktop app or in the Docker/Fly image. Without this, a Code node
|
|
56
|
+
* body in production runs with no toolbelt (`nodetool.capabilities()` is `{}`,
|
|
57
|
+
* `yt_dlp` is missing) and cannot import
|
|
58
|
+
* `@nodetool-ai/sandbox-nodetool/*` at all.
|
|
59
|
+
*
|
|
60
|
+
* The server calls this at bootstrap with the copy esbuild already inlined,
|
|
61
|
+
* which also keeps the run on one module instance — a staged second copy would
|
|
62
|
+
* carry its own capability registry.
|
|
63
|
+
*/
|
|
64
|
+
export declare function setCodeNodeAgentsModule(mod: AgentsModule | null): void;
|
|
65
|
+
/**
|
|
66
|
+
* Test/host injection point: replace the assembled toolbelt with a fixed set
|
|
67
|
+
* (`[]` simulates a beltless host). Pass `null` to restore the default
|
|
68
|
+
* assembly.
|
|
69
|
+
*/
|
|
70
|
+
export declare function setCodeNodeTools(tools: AgentTool[] | null): void;
|
|
71
|
+
/**
|
|
72
|
+
* Bags a node invocation may hold before `emit` blocks the guest. Exported so
|
|
73
|
+
* a test can drive the producer past the bound without guessing the number.
|
|
74
|
+
*/
|
|
75
|
+
export declare const EMIT_CHANNEL_CAPACITY = 64;
|
|
76
|
+
export declare class CodeNode extends BaseNode {
|
|
77
|
+
static readonly nodeType = "nodetool.code.Code";
|
|
78
|
+
static readonly platforms: readonly import("@nodetool-ai/protocol").Platform[];
|
|
79
|
+
static readonly title = "Code";
|
|
80
|
+
static readonly description: string;
|
|
81
|
+
static readonly inlineFields: string[];
|
|
82
|
+
static readonly inputFields: never[];
|
|
83
|
+
static readonly supportsDynamicInputs = true;
|
|
84
|
+
static readonly supportsDynamicOutputs = true;
|
|
85
|
+
/**
|
|
86
|
+
* Whether this node streams its inputs is a property of its body, not of the
|
|
87
|
+
* type: a body that calls `stream` drains its own inbox in {@link run},
|
|
88
|
+
* every other body keeps the buffered per-item invocation. Both hydration
|
|
89
|
+
* paths re-read the body through this hook, so editing the last `stream`
|
|
90
|
+
* call out flips the mode back with no saved flag involved.
|
|
91
|
+
*/
|
|
92
|
+
static readonly resolveStreamingInput: NonNullable<typeof BaseNode.resolveStreamingInput>;
|
|
93
|
+
/**
|
|
94
|
+
* Persistent state across streaming invocations; reset each workflow run.
|
|
95
|
+
* The guest owns the contents and the sandbox syncs them back marshalled, so
|
|
96
|
+
* what survives a call is what survives a JSON round trip.
|
|
97
|
+
*/
|
|
98
|
+
private _state;
|
|
99
|
+
code: string;
|
|
100
|
+
secrets: string[];
|
|
101
|
+
script: JsScriptLink | Record<string, never>;
|
|
102
|
+
timeout: number;
|
|
103
|
+
max_response_mb: number;
|
|
104
|
+
allow_local_network: boolean;
|
|
105
|
+
allow_host_filesystem: boolean;
|
|
106
|
+
initialize(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Forward guest `progress(percent, message)` calls to the kernel as
|
|
109
|
+
* `node_progress` messages — the same channel the Python worker and the
|
|
110
|
+
* ComfyUI node use, so the editor's node progress bar picks them up.
|
|
111
|
+
*/
|
|
112
|
+
private progressSink;
|
|
113
|
+
/**
|
|
114
|
+
* Forward guest `console.*` calls as `log_update` messages — the same
|
|
115
|
+
* channel package-resolution warnings already use, so the editor's log
|
|
116
|
+
* panel shows them as they happen.
|
|
117
|
+
*/
|
|
118
|
+
private logSink;
|
|
119
|
+
/** Post on the node's log channel, where the run has one. */
|
|
120
|
+
private postLog;
|
|
121
|
+
/**
|
|
122
|
+
* Sandbox policy from the node's props. The sandbox clamps
|
|
123
|
+
* `maxResponseBodyBytes` to its own ceiling, so no clamping here.
|
|
124
|
+
*
|
|
125
|
+
* The two capability switches default to the restrictive value and are only
|
|
126
|
+
* ever widened by an explicit choice on this node — nothing the guest code
|
|
127
|
+
* can reach.
|
|
128
|
+
*/
|
|
129
|
+
private sandboxLimits;
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the packs the body imports through the host's catalog.
|
|
132
|
+
*
|
|
133
|
+
* The imports are the declaration: a static `import` of an installed pack is
|
|
134
|
+
* served, and nothing else is — same rule a JS script runs under, so a body
|
|
135
|
+
* moved between the two behaves identically. A body that imports no pack gets
|
|
136
|
+
* no loader at all. An import that cannot be served — no catalog in this
|
|
137
|
+
* process, an uninstalled pack, a module the host refused — fails the node
|
|
138
|
+
* here rather than as a resolve error inside the guest. Version and digest
|
|
139
|
+
* drift only warn: resolution uses what is installed, and saying so on the
|
|
140
|
+
* node's log is the whole remedy.
|
|
141
|
+
*
|
|
142
|
+
* NodeTool's own guest modules (`@nodetool-ai/sandbox-nodetool/*`) are not
|
|
143
|
+
* packs; {@link CodeNode.resolveCapabilities} mounts those.
|
|
144
|
+
*/
|
|
145
|
+
private resolveModules;
|
|
146
|
+
/** Post a warning on the node's log channel, where the run has one. */
|
|
147
|
+
private logWarning;
|
|
148
|
+
/**
|
|
149
|
+
* The globals every run gets, on either contract. Declared inputs arrive on
|
|
150
|
+
* one `inputs` object rather than as globals of their own name: sharing the
|
|
151
|
+
* global namespace with the sandbox API let an input called `env` or `data`
|
|
152
|
+
* shadow a bridge, and made every undeclared identifier ambiguous between a
|
|
153
|
+
* typo and a missing slot. State stays a direct reference so mutations
|
|
154
|
+
* persist across calls.
|
|
155
|
+
*/
|
|
156
|
+
private sandboxGlobals;
|
|
157
|
+
/** Milliseconds the guest may run, or undefined for no limit. */
|
|
158
|
+
private timeoutMs;
|
|
159
|
+
/**
|
|
160
|
+
* What this invocation runs: the node's own properties, always.
|
|
161
|
+
*
|
|
162
|
+
* Linking a script *materializes* it — the pinned version's code, secrets
|
|
163
|
+
* and timeout are copied onto the node when the link is made or
|
|
164
|
+
* updated, and the `script` property records only which version they came
|
|
165
|
+
* from. So execution needs no database, no resolver, and no await: the body
|
|
166
|
+
* a run executes is the body the graph carries, which is also the body
|
|
167
|
+
* hydration read to decide whether this node streams its inputs.
|
|
168
|
+
*/
|
|
169
|
+
private envelope;
|
|
170
|
+
/**
|
|
171
|
+
* Everything a run of this node's body needs regardless of contract: the
|
|
172
|
+
* prelude, the node's own policy, its globals and its module resolution.
|
|
173
|
+
* Every execution path starts from this and adds only what its own contract
|
|
174
|
+
* needs (an emit sink, an input bridge, a cancellation signal).
|
|
175
|
+
*/
|
|
176
|
+
private sandboxOptions;
|
|
177
|
+
/**
|
|
178
|
+
* NodeTool's own guest modules (`@nodetool-ai/sandbox-nodetool/*`) for the
|
|
179
|
+
* namespaces this body imports, or nothing when it imports none.
|
|
180
|
+
*
|
|
181
|
+
* They are not packs, so they never reach the catalog: the host decides what
|
|
182
|
+
* a run can reach, and it says so with the gate it put on the context. The
|
|
183
|
+
* run carries that gate, exactly as the belt does — the import path and the
|
|
184
|
+
* belt bridge are two doors onto the same capabilities, so one gated and the
|
|
185
|
+
* other not would be no gate at all. It is the wiring
|
|
186
|
+
* `mountJsScriptSandbox` builds for a JS script, over the Code node's own
|
|
187
|
+
* module resolution.
|
|
188
|
+
*/
|
|
189
|
+
private resolveCapabilities;
|
|
190
|
+
process(context?: ProcessingContext): Promise<CodeOutputs>;
|
|
191
|
+
/**
|
|
192
|
+
* Streaming-input execution: one invocation that drains its own inbox.
|
|
193
|
+
*
|
|
194
|
+
* The kernel calls this instead of the per-item path when the hydrated
|
|
195
|
+
* `is_streaming_input` flag is true, which for this node means the body
|
|
196
|
+
* calls `stream` (see {@link CodeNode.resolveStreamingInput}). The body runs
|
|
197
|
+
* once; `stream(name)` / `stream.any()` pull values out of `inputs` one take
|
|
198
|
+
* at a time, `emit` routes live through `outputs.emit`, and the `output`
|
|
199
|
+
* finals post as one bag when the body ends.
|
|
200
|
+
*/
|
|
201
|
+
run(inputs: StreamingInputs, outputs: StreamingOutputs, context?: ProcessingContext): Promise<void>;
|
|
202
|
+
/** The deprecated return-bag path: implicit return, normalized output. */
|
|
203
|
+
private runLegacySingleShot;
|
|
204
|
+
genProcess(context?: ProcessingContext): AsyncGenerator<CodeOutputs>;
|
|
205
|
+
/**
|
|
206
|
+
* The emit/output pump: start the run, hand every `emit` bag downstream as
|
|
207
|
+
* it happens, then post the final `output` bag once the run succeeds.
|
|
208
|
+
*
|
|
209
|
+
* The guest's `emit` promise resolves only when the channel has accepted its
|
|
210
|
+
* bag, so a producer faster than the kernel blocks at the channel bound —
|
|
211
|
+
* backpressure with no new kernel concept.
|
|
212
|
+
*/
|
|
213
|
+
private pumpEmitContract;
|
|
214
|
+
/** The deprecated `yield` path: the guest collects, the host replays. */
|
|
215
|
+
private runLegacyStream;
|
|
216
|
+
}
|
|
217
|
+
export {};
|
|
218
|
+
//# sourceMappingURL=code-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-node.d.ts","sourceRoot":"","sources":["../../src/nodes/code-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EACL,QAAQ,EAYR,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,KAAK,gBAAgB,EAKtB,MAAM,gCAAgC,CAAC;AAWxC,gFAAgF;AAChF,KAAK,WAAW,GAAG,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;AAyF5D,0EAA0E;AAC1E,KAAK,YAAY,GAAG,cAAc,qBAAqB,CAAC,CAAC;AACzD,KAAK,SAAS,GAAG,YAAY,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AAkBpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAGtE;AAqBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,GAAG,IAAI,CAEhE;AAoDD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAqJxC,qBAAa,QAAS,SAAQ,QAAQ;IACpC,MAAM,CAAC,QAAQ,CAAC,QAAQ,wBAAwB;IAChD,MAAM,CAAC,QAAQ,CAAC,SAAS,sDAAiB;IAC1C,MAAM,CAAC,QAAQ,CAAC,KAAK,UAAU;IAC/B,MAAM,CAAC,QAAQ,CAAC,WAAW,SAqB8C;IACzE,MAAM,CAAC,QAAQ,CAAC,YAAY,WAAY;IACxC,MAAM,CAAC,QAAQ,CAAC,WAAW,UAAM;IACjC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,QAAQ;IAC7C,MAAM,CAAC,QAAQ,CAAC,sBAAsB,QAAQ;IAC9C;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CAAC,qBAAqB,EAAE,WAAW,CAChD,OAAO,QAAQ,CAAC,qBAAqB,CACtC,CAA2E;IAE5E;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAsB;IA+C5B,IAAI,EAAE,MAAM,CAAC;IAab,OAAO,EAAE,MAAM,EAAE,CAAC;IAiBlB,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAQ7C,OAAO,EAAE,MAAM,CAAC;IAShB,eAAe,EAAE,MAAM,CAAC;IAWxB,mBAAmB,EAAE,OAAO,CAAC;IAY7B,qBAAqB,EAAE,OAAO,CAAC;IAEjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhC;IAED;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAgBpB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IASf,6DAA6D;IAC7D,OAAO,CAAC,OAAO;IAgBf;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAkBrB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,cAAc;IA8CtB,uEAAuE;IACvE,OAAO,CAAC,UAAU;IAOlB;;;;;;;OAOG;YACW,cAAc;IAc5B,iEAAiE;IACjE,OAAO,CAAC,SAAS;IAMjB;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ;IAQhB;;;;;OAKG;YACW,cAAc;IAkB5B;;;;;;;;;;;OAWG;YACW,mBAAmB;IAqB3B,OAAO,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CA0B/D;IAED;;;;;;;;;OASG;IACG,GAAG,CACP,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,gBAAgB,EACzB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAwDf;IAED,0EAA0E;YAC5D,mBAAmB;IAe1B,UAAU,CACf,OAAO,CAAC,EAAE,iBAAiB,GAC1B,cAAc,CAAC,WAAW,CAAC,CAa7B;IAED;;;;;;;OAOG;YACY,gBAAgB;IAuD/B,yEAAyE;YAC1D,eAAe;CAoC/B"}
|