The published API
Every package here releases in lock step. nx.json leaves release.projectsRelationship at Nx’s fixed setting, so one version number covers all of them and there is no combination of versions to test. Which of them you install follows from what you are trying to do.
| The job | The package | The entry point |
|---|---|---|
| Run a program in this process | @supolka/codemode-executor |
IsolateExecutor |
| Run a program in a daemon over the network | @supolka/cloudflare-codemode-executor |
connect |
| Serve the protocol to other processes | @supolka/codemode-executor/server |
createExecutorServer |
| Serve the tool calls a program makes | @supolka/codemode-executor-protocol |
createEnvelopeDispatch |
Replace DynamicWorkerExecutor in a @cloudflare/codemode host |
@supolka/cloudflare-codemode-executor |
CodemodeExecutor |
| Implement the contract on another runtime | @supolka/codemode-executor-protocol |
Executor and ExecutorApi |
Symbol by symbol, the executor protocol says what these mean, and Failures says what each can fail with. No lane of pnpm verify reads this page against the packages, so the listings below are transcribed and not derived. But the pool’s event payloads are the exception, and the paragraph that lists them names the test that holds them to the code.
@supolka/codemode-executor-protocol
Section titled “@supolka/codemode-executor-protocol”This package is the contract both sides implement. It declares no dependency and names no runtime API, so it compiles wherever a host runs, and every export below leaves through the package root. Types a caller writes against describe one execution and what comes back from it.
| Export | What it is |
|---|---|
Executor |
The in-language contract: execute, invokeHandle, closeHandles, dispose. Both shipped executors implement it. |
ExecuteRequest |
One execution stated in full: code, providers, dispatch, modules, timeout, countHostWaits, outbound, retainHandles. |
ExecuteResult |
What comes back: result, error, logs, and executionId with handles when a run retained them. |
ExecuteResultMessage |
The same envelope on the wire, where the run’s own value is resultJson, the JSON text the isolate produced. |
ExecuteCallOptions |
The options half of the wire execute, carrying timeout, countHostWaits, modules and retainHandles. |
ProviderDescriptor |
A provider as the sandbox is told about it: name, tools, and the optional positionalArgs, prelude and proxySource. |
ModuleMap |
The names a program may import, each a ModuleSource. |
ModuleSource |
One module: a source string, or an entry keyed js, cjs, json, text or data. |
ModuleEntry |
The wire form of one map entry, the pair [name, source]. |
ToolDispatchFn |
(provider, tool, argsJson) => Promise<string>, the function that serves a tool call. |
OutboundFetchFn |
(request) => Promise<response>, the function that serves sandbox fetch when the caller supplies one. |
OutboundRequestMessage, OutboundResponseMessage |
A request and a response as plain data, used by outbound requests and by handle invocations. |
OutboundMode |
The wire form of the outbound setting: native, stub or disabled. |
LogLine |
A captured console line, carrying its level and its text. |
Constants below are the contract’s numbers and its pinned texts. Importing them is safer than repeating the values, because a host that inlines one is holding a copy that stays at the value it was written with.
| Export | Value | What it governs |
|---|---|---|
PROTOCOL_VERSION |
1 |
The handshake, where the two versions must be exactly equal. |
DEFAULT_TIMEOUT |
60000 |
The run bound when neither the request nor the server names one, one minute. |
MAX_SUPPORTED_TIMEOUT |
2147483647 |
The widest delay a 32-bit timer accepts, close to 25 days. A null timeout resolves to it. |
PAYLOAD_LIMIT |
8388608 |
The largest single value one execution may send outward, 8 × 1024 × 1024 characters. |
CONSOLE_CAPTURE_LIMIT |
2097152 |
The captured console text of a whole run, 2 × 1024 × 1024 characters. |
TIMEOUT_MESSAGE |
Execution timed out |
The phrase the codemode dialect pins. |
EXECUTION_COMPLETED_MESSAGE |
Execution has already completed. |
The answer to a tool call that lands after the run settled. |
CONSOLE_CAPTURE_MESSAGE |
Composed from CONSOLE_CAPTURE_LIMIT |
The last line a run that filled its console budget receives, quoted on Failures. |
One codec turns values into the one JSON string the dispatch grammar carries, binary included.
| Export | What it does |
|---|---|
createEnvelopeDispatch(state, respond) |
Wraps a plain responder into a ToolDispatchFn: it serializes the envelope, answers a late call with the completed text, and turns a rejection into {error}. |
parseDispatchArguments(argsJson) |
Turns the arguments string into an argument list, where the empty string is no arguments and a non-array payload is one argument. |
stringifyForDispatch(value), parseForDispatch(json) |
The codec itself, tagging a Uint8Array, an ArrayBuffer or a typed array as base64. |
decodeDispatchValue(value), DecodedDispatchValue<T> |
Rebuilds binary inside a value that already crossed as data, and the type that mirrors it. |
The failure surface is ExecutorError, ExecutorErrorCode, rehydrateExecutorError, describeFailure, the PayloadPart union and one class per declared failure. Failures enumerates every code and every text. The rest of the package is for whoever implements the contract, not for a host calling an executor that already does. ExecutorApi is the RPC surface a server serves. assertProtocolCompatible and assertOutboundFetch are the checks a server runs on what arrives: the first refuses a version that is not exactly its own, the second refuses a stub outbound mode that came without its function. validateProviders holds a provider list to RESERVED_PROVIDER_NAMES, PROVIDER_NAME_PATTERN and JAVASCRIPT_RESERVED_WORDS, and Failures says what each of those refuses. separateImports splits the leading import block off an entry, and the adapter reuses it when it normalizes model output.
Most hosts reach this package for one job: serving the tool calls a program makes. createEnvelopeDispatch with parseDispatchArguments is all of it, and the NestJS example is written that way:
import { createEnvelopeDispatch, parseDispatchArguments } from '@supolka/codemode-executor-protocol'
const state = { active: true }
try { return await executor.execute({ code, providers: PROVIDERS, outbound: null, dispatch: createEnvelopeDispatch(state, async (provider, tool, argsJson) => callTool(provider, tool, parseDispatchArguments(argsJson)), ), })} finally { state.active = false}@supolka/codemode-executor
Section titled “@supolka/codemode-executor”This package is the Node core. Its root export runs executions, and each subpath under it opens one layer of the machinery below, for a host that wants to build on a part and not on the whole.
Root export
Section titled “Root export”Hosts construct IsolateExecutor to run programs in their own process. Constructing it spawns nothing. The first execution starts the pool, and dispose ends the children:
import { IsolateExecutor } from '@supolka/codemode-executor'
const executor = new IsolateExecutor({ timeout: 10_000, memoryLimit: 128 })
const { result, error, logs } = await executor.execute({ code: 'async () => { const users = await store.listUsers(); return users.length }', providers: [{ name: 'store', tools: ['listUsers'] }], dispatch, outbound: null,})
await executor.dispose()Every field of IsolateExecutorOptions sets a construction-time default. A request field wins over that default for timeout, countHostWaits, modules and outbound. memoryLimit and pool are settled when the executor is built, and no request moves them.
| Option | Default | Declared in |
|---|---|---|
timeout |
60000 ms, and null lifts the bound to MAX_SUPPORTED_TIMEOUT |
packages/protocol/src/protocol.ts |
memoryLimit |
128, in megabytes |
packages/executor/src/executor/defaults.ts |
countHostWaits |
false, so a wait the caller serves stays outside the bound |
packages/executor/src/executor/isolate-executor.ts |
outbound |
Absent, and the executor performs requests natively; null closes the network |
packages/executor/src/executor/isolate-executor.ts |
modules |
Absent | packages/executor/src/executor/isolate-executor.ts |
pool |
The PoolOptions defaults below |
packages/executor/src/pool/pool.ts |
DEFAULT_GRACE is the grace period an execution gets after its bound, one second, in packages/executor/src/executor/defaults.ts. But it is not an option of this class. A host that needs a different one sets grace on the request it hands to WorkerPool or Sandbox, or sets executor.defaults.grace on a server.
One more export from the root is a result limiter. It caps what a host shows, not what the executor allows. limitExecutionResultValue(value, responseLimit) answers an IntactExecutionResult or a TruncatedExecutionResult, and formatLimitedExecutionOutput renders either one with a truncation note. ExecutorError, ExecutorErrorCode, rehydrateExecutorError, MAX_SUPPORTED_TIMEOUT, ExecuteRequest, ExecuteResult and Executor are re-exported from the protocol package, so a host that imports the core alone can still branch on codes. Logger and PoolOptions come along for a host that configures the pool from here.
./server subpath
Section titled “./server subpath”createExecutorServer(options) answers a ServerHandle that owns a pool and speaks the protocol over WebSocket. The daemon is this function plus configuration:
import { createExecutorServer } from '@supolka/codemode-executor/server'
const server = createExecutorServer({ port: 8080, responseLimit: 8 * 1024 * 1024, executor: { pool: { maxWorkers: 4 }, defaults: { maxTimeout: 120_000 } },})
await server.listen()That handle carries listen, close, address, stopAccepting, resumeAccepting, the accepting flag and the pool itself. Turning acceptance off is the drain: open connections keep working and their executions keep running, but the next caller is turned away and /readyz says so. A deployment sets ServerOptions. Daemons map their environment onto this table, and the operator guide documents that mapping setting by setting.
| Option | Default | What it decides |
|---|---|---|
host, port |
0.0.0.0 and 8080 |
Where the listener binds. |
socketPath |
Absent | A unix socket instead of a host and port. |
onConnection |
Absent, which admits everyone as anonymous |
The identity on the handshake, or a rejection. It reads a ConnectionHandshake and answers a ConnectionDecision, and a hook that throws rejects the connection. |
executor.pool |
The PoolOptions defaults |
The pool behind the server. |
executor.outboundMode |
Absent | The operator’s veto: null denies the native mode however a caller asked. |
executor.defaults |
timeout 60000 ms, grace 1000 ms and memoryLimit 128 MB, applied in packages/executor/src/server/bridge.ts |
The bounds a request falls back to, plus maxTimeout, which caps every requested timeout. |
responseLimit |
null, which adds no cap beyond the protocol’s own bound |
The bytes one result envelope or handle answer may carry. |
maxConnectionAge |
Absent | How long one connection may stay open before the server closes it. |
pingInterval |
30000 |
How often a connection is asked whether it is still there, every thirty seconds. |
shutdownDelay |
0 |
How long close keeps serving after readiness flips. |
metrics |
Absent | A MetricsSource that makes /metrics answer a scrape. |
instrumented |
true |
Whether the connection metrics and the request span are built. |
logger |
Absent | Where the server writes. |
runBridgedExecution, BridgeRequest, ExecutionDefaults and ServerOutboundMode are for an implementer serving this protocol over a transport of its own. runBridgedExecution is the leg between an arriving RPC call and the pool. It owns the provider-name check, the response limit and the stub lifetimes, so a transport built on it inherits every one of them without writing any.
./pool subpath
Section titled “./pool subpath”WorkerPool is the supervisor under both executors. A host reaches for it directly to run its own protocol on top, or to subscribe to what the pool publishes. Its surface is execute, invokeHandle, closeHandles, hasHandles, shutdown, the queueFull flag and the events emitter. Telemetry lists every event and its payload. PoolOptions is where the shape of the deployment lives, and every default below is declared in packages/executor/src/pool/pool.ts.
| Option | Default | What it decides |
|---|---|---|
maxWorkers |
availableParallelism() |
How many child processes run at once. |
executionsPerWorker |
1 |
How many executions one child serves at a time. |
maxQueue |
100 |
How many executions may wait before QUEUE_OVERFLOW. |
retireAfterExecutions |
50 |
How many executions a worker serves before it is replaced. |
workerMemoryLimit |
536870912 |
The process memory, in bytes, past which a worker is retired, 512 MiB. |
terminationDelay |
5000 |
How long past its timeout and grace a hung execution’s worker survives before SIGKILL, five seconds. |
handlesIdleTimeout |
300000 |
How long a retained execution survives with no invocation, five minutes. |
childEntryPath |
The package’s own child entry | Which module a worker forks. |
childExecArgv |
['--no-node-snapshot'] |
The flags a child runs under. isolated-vm requires this one on every Node this package supports. |
childEnv, childCwd |
process.env and the parent’s directory |
The environment a child inherits. |
instrumented |
true |
Whether the pool builds instruments and traces. |
logger |
Absent | Where the pool writes. |
Every payload PoolEvents names is exported here, so a subscriber has a name for what its handler receives: ExecutionStartEvent, ExecutionSettleEvent, ExecutionRejectEvent, WorkerSpawnEvent, WorkerCrashEvent, WorkerRetireEvent, HandlesCloseEvent and QueueDepthEvent, along with PoolEvents itself, HandlesCloseReason, RetireReason and PoolExecuteRequest. A test holds it to the code. packages/executor/src/pool/index.spec.ts compares each payload against the event map by type, and stops compiling if one of them ever stops being exported from this subpath.
./sandbox subpath
Section titled “./sandbox subpath”Sandbox is one isolate per execution with no process supervision around it, for an embedder that supervises its own children. Hosts that want the crash boundary of a child process want WorkerPool or IsolateExecutor instead. A memory limit is enforced by the engine and survives here, but a guest that takes the whole process down takes this one with it. Composing a sandbox reads the runtime snapshot, so a process that composes one at startup has it before the first request arrives. The pool’s child composes exactly one, at startup.
import { Sandbox } from '@supolka/codemode-executor/sandbox'
const sandbox = new Sandbox({ // Shared across executions, so a module compiled once is not compiled again. cachedDataStore: new Map(), hooks: { onExecutionSettled: ({ duration, outcome }) => record(duration, outcome) },})
const answer = await sandbox.execute({ code: 'async () => 6 * 7', providers: [], timeout: 30_000, grace: 1_000, memoryLimit: 128, outbound: null,})Both members of the constructor options are optional, though the object itself is not, so the bare form is new Sandbox({}). SandboxExecuteRequest defaults no field for the caller: code, providers, timeout, grace, memoryLimit and outbound are all required, because this layer holds no configuration of its own to fall back on. Optional members are modules, dispatch, countHostWaits, retainHandles and the host-wait reporter the pool’s child uses to pause the run clock. outbound reads the same three ways it reads everywhere: a function serves the request, null closes the network, and undefined lets the executor perform it. execute answers an ExecuteResult. It returns user-code failures inside the envelope and does not reject for them, so a syntax error, a bad provider list, a resolution failure, a timeout and a user throw all arrive as error on a resolved promise. invokeHandle and closeHandles serve retained executions, hasHandles answers whether an execution still holds any, and SandboxHooks.onExecutionSettled receives a SandboxExecutionSettledEvent carrying the duration and whether the run succeeded. A hook that throws is swallowed and never fires twice.
@supolka/cloudflare-codemode-executor
Section titled “@supolka/cloudflare-codemode-executor”This package holds the dialect adapter and the remote executor, in JavaScript that names no runtime API, so any JavaScript runtime can host it. The testbed proves that by running the client inside workerd with nodejs_compat off. connect(url, options) opens a session to an executor server and answers a RemoteExecutor. That object serves the same Executor contract IsolateExecutor serves:
import { CodemodeExecutor, connect } from '@supolka/cloudflare-codemode-executor'
const executor = new CodemodeExecutor(await connect('ws://executor:8080'))ConnectOptions decides how the connection behaves, and its defaults are declared in packages/cloudflare-codemode/src/remote-executor.ts.
| Option | Default | What it decides |
|---|---|---|
websocket |
The runtime’s own WebSocket |
The constructor the session builds its socket from. Handshake headers need one passed here. |
headers |
Absent | The handshake headers, which require websocket. |
rotateAfterExecutions |
100 |
How many executions one session serves before it is retired and reopened. |
reconnect |
{ retries: 3, backoff: 250 }, and false disables it |
How a call that finds no live session reopens one, backing off 250 ms, then 500, then 750. A version mismatch never retries. |
timeout, countHostWaits, modules, outbound |
Absent | The per-connection defaults a request field overrides. |
Retention has no connection-time default on purpose. retainHandles rides the request alone, because keeping the isolate is tied to reading the granted executionId back, and a connection-wide flag would grant handles to callers who never asked to hold one.
CodemodeExecutor wraps any native executor into the @cloudflare/codemode dialect, in process or remote without knowing which. Everything the dialect pins is its responsibility: the raw model output cleanup, the reserved names, the tool name sanitization, the log prefixes, the connector control markers, and rendering the native timeout text back down to the one sentence the dialect expects. ExtendedExecuteOptions adds modules, timeout and outbound per call, so a host that bundles a fresh module graph per execution keeps one warm executor. ExtendedProvider adds positionalArgs and proxySource to a dialect provider. Moving a host onto this executor says what the move costs a migrating host.
This package re-exports ExecutorError, rehydrateExecutorError, ExecuteRequest, ExecuteResult, Executor, ExecutorErrorCode, LogLine, ModuleMap and OutboundFetchFn from the protocol package, so a host on another runtime installs this one alone. The rest of the surface exists for whoever builds on the dialect, not for a host that only calls it. normalizeCode turns whatever a model emitted into the entry form. sanitizeToolName, detectSanitizeCollision and formatSanitizeCollision are the dialect’s tool-name rules and its pinned collision text. planExecution and validateExecution, with ExecutionPlan and ToolHandler, turn dialect providers and connectors into descriptors and handlers. The class RemoteExecutor and the type WebSocketConstructor are exported for a host that names them in its own signatures. Naming them is the only reason to reach past connect.