code-runner
API Reference

The Wire Contract

One JSON Schema, generating TS types, Zod validators, and Go structs.

Every type that crosses a process boundary in code-runner — request bodies, events, job specs, Redis payloads — is defined once in packages/contract/schema/wire.schema.json. From that single source, codegen produces:

  • TypeScript types (consumed by the API and the SDKs),
  • Zod validators (the API validates request bodies with these),
  • Go structs (the worker imports packages/contract/gen/go/wire).

A CI drift check (make contract-check) regenerates the artifacts and fails if they differ from what's committed — so the polyglot seam can never silently drift.

Never hand-edit anything under packages/contract/gen/**. To change the wire format, edit wire.schema.json and run make contract (or pnpm contract).

Shared names: keys, channels, events

The contract package also exports the canonical Redis keys, channel builders, and event names from @teovilla/code-runner-contract. The Go worker mirrors these in internal/keys.

import { keys, channelForJob, stdinChannel, controlChannel, events } from '@teovilla/code-runner-contract';

channelForJob('abc');   // "private-run-abc"   (soketi output channel)
stdinChannel('abc');    // "stdin:abc"         (Redis pub-sub: stdin)
controlChannel('abc');  // "ctrl:abc"          (Redis pub-sub: start/kill/stdin_close)

keys.jobQueue;          // "jobs:queue"        (Redis list)
keys.jobSpec('abc');    // "job:abc:spec"
keys.jobStatus('abc');  // "job:abc:status"
keys.jobOutput('abc');  // "job:abc:output"

events;                 // { stage, stdout, stderr, compileOutput, result, artifact }

Core types

These are exported from @teovilla/code-runner-contract and re-exported by the SDKs.

TypeShape
ExecuteRequest{ language, version?, files: FileInput[], limits?: LimitsOverride, collectOutput? }
ExecuteResponse{ jobId, channel, status: "queued" }
FileInput{ name: string, content?: string, encoding?: "utf8" | "base64", ref?: string } — exactly one of content or ref
JobStatus{ jobId, channel, language, version, state: JobState, updatedAtMs }
JobState"queued" | "starting" | "running" | "done" | "killed" | "error"
Limits{ wallTimeMs, idleMs, cpuMs, memoryMb, pids, outputKb, maxArtifacts, maxArtifactBytes }
LimitsOverrideall Limits fields optional
LanguageInfo{ language, version, aliases, interactive }
Manifest{ language, version, aliases, image, entrypoint, compile?, run, interactive, defaultLimits }

A FileInput carries exactly one of content (inline bytes, interpreted per encoding"utf8" text by default or "base64" for arbitrary bytes) or ref ("sha256:<64-hex>", a pre-uploaded content-addressed blob). The XOR is not expressible in JSON Schema in a way that survives codegen — both fields generate as optional — so it is enforced at runtime in both the API (zod refinement) and the worker (validateFileInput); never trust one side. ref and encoding are purely additive: existing inline { name, content } callers are unaffected. See Multi-file input.

Blob message types

The content-addressed blob store (CAS) is driven by two request/response pairs. See Large & shared files.

TypeShapeEndpoint
BlobCheckRequest{ hashes: string[] }POST /v1/blobs/check request
BlobUpload{ hash: string, uploadUrl: string }one missing-blob entry
BlobCheckResponse{ missing: BlobUpload[], present: string[] }POST /v1/blobs/check response
BlobFinalizeRequest{ hashes: string[] }POST /v1/blobs/finalize request
BlobFinalizeResponse{ finalized: string[] }POST /v1/blobs/finalize response

All hashes are "sha256:<64 lowercase hex>". uploadUrl is a presigned PUT URL the SDK uploads bytes to directly (client → store); the bytes never transit the API.

Event & message types

TypeShapeCarried on
StageEvent{ phase: "queued" | "compiling" | "running" }soketi stage
OutputChunkEvent{ chunk: string, seq: number }soketi stdout / stderr / compile_output
ResultEvent{ exitCode, signal, timedOut, idleTimedOut, truncated, durationMs }soketi result
Artifact{ name, mimeType, bytes, url }soketi artifact / RunResult
CompileResult{ exitCode: number | null, signal: string | null, stdout, stderr, output, durationMs }RunResult.compile
RunResultResultEvent + { stdout, stderr, compile?: CompileResult, artifacts: Artifact[], artifactsTruncated }GET /v1/jobs/:id/output
StdinMessage{ chunk: string }Redis stdin:<id>
ControlMessage{ type: "start" | "kill" | "stdin_close" }Redis ctrl:<id>

RunResult.compile is present only for compiled languages that actually ran a compile step — it's absent for interpreted languages (e.g. Python) or when no compile ran. Build logs live in compile and are kept distinct from the run stdout / stderr (which cover the run stage only). CompileResult.output is the interleaved compile stdout + stderr in emission order (mirroring Piston's compile.output). A non-zero compile.exitCode means compilation failed and the run stage did not execute: the run stdout / stderr are empty and the top-level exitCode reflects the compile failure.

Regenerating the contract

pnpm contract        # or: make contract — regenerate TS + Zod + Go
make contract-check  # CI gate: fail if generated artifacts drifted

The codegen uses go-jsonschema for the Go structs and a TypeScript/Zod generator for the JS side.

On this page