@womp/kakapo-sdk
v0.8.0
Published
Typed Node.js and browser SDK for controlling Kakapo over WebSocket
Keywords
Readme
@womp/kakapo-sdk
Migrating to 0.7
Use accessScene(callback, { save: true }) for a single fetch, isolated draft, validation,
patch and optional save. Its result includes value, changed, save and the acknowledged
scene. editScene and editSceneAndSave remain compatibility shortcuts.
getSceneRevision() and getSceneSnapshotRevision() were removed, along with fingerprint-based
conflict checks. Hosts must serialize scene access and suspend competing manual writes;
independent clients have no atomic cross-client protection. Cached state is invalidated after access.
Camera RPC changes made during access are preserved without copying the full scene.
The existing renderer-readiness wait remains until supported native engines provide readiness guarantees.
Typed Node.js and browser SDK for manipulating a Kakapo scene through its JSON-RPC WebSocket.
Releases
Pushes to main publish an unpublished stable package.json version newer than npm's latest;
already-published versions skip. No Git tag is required. Deliberately bump MAJOR.MINOR.PATCH;
prereleases/build metadata are not published automatically. CI uses the frozen Yarn lockfile,
typechecks, unit tests and a fresh tarball import check, then publishes that same archive and verifies
its registry integrity. Live renderer tests are not run. Registry failures block publication.
Releases are serialized; newer pending pushes may supersede older pending runs.
Configure npm's trusted publisher once for @womp/kakapo-sdk: organization wompxyz, repository
kakapo-sdk, workflow filename publish.yml, no environment, and allow direct npm publish.
GitHub-hosted runners use OIDC without a publishing token. After an uncertain publish, inspect npm
before rerunning the failed workflow. Publish the SDK before updating dependent applications.
yarn install
yarn buildimport { KakapoAPI } from "@womp/kakapo-sdk";
const kakapo = new KakapoAPI();
await kakapo.connect({ refreshScene: false });
const { value: union } = await kakapo.accessScene((scene) => {
const root = scene.createNode({
kind: "union",
parentId: 0,
name: "Agent Model",
});
const box = scene.createNode({
kind: "primitive",
parentId: root.id,
name: "Body",
properties: {
primitive: "box",
transform: { scale: { x: 10, y: 4, z: 6 } },
},
});
const red = scene.createMaterial({
color: { x: 1, y: 0.05, z: 0.05 },
roughness: 0.4,
});
const body = scene.node(box.id, "primitive");
body.materialId = red.id;
body.position = { x: 0, y: 4, z: 0 };
body.round = 0.2;
body.save();
return root;
});
console.log(await kakapo.getNodeBoundingBox(union.id));
kakapo.disconnect();The transport uses the browser's native WebSocket when bundled for the web and loads ws
dynamically in Node.js.
Applications that already own a renderer connection can pass a KakapoAPITransport as
new KakapoAPI({ transport }). The adapter defines connection lifecycle, RPC, and token responses.
captureScreenshot({}) requests the active camera's JPEG without automatic framing or a cached scene.
Pass camera to frame explicitly, or call setCamera(camera) without taking a screenshot:
await kakapo.captureScreenshot({ camera: {
position: { x: 30, y: 20, z: 40 },
target: { x: 0, y: 0, z: 0 },
projection: "perspective",
horizontalFovDegrees: 60,
} });Camera coordinates are native Kakapo world coordinates: Y is up, and one unit is 25 mm.
These are not Womp editor coordinates: editor millimetres (x, y, z) map to native
(-z / 25, y / 25, -x / 25). SDK renderer bounds use native coordinates; only use bounds
known to be in world space when choosing a world-space target.
Perspective framing uses horizontal FOV in degrees. Orthographic framing uses
orthographicWidth, the visible horizontal span in native units. Omitted projection and lens
settings retain their active values, including focus/aperture; no depth-of-field controls are exposed.
Position and target determine direction and world-Y up. Exact vertical orientations, coincident
positions, and values outside the native controller's supported range are rejected.
Camera changes are immediate, without animation, and remain active after capture.
The SDK never fits bounds or restores an earlier camera. SDK camera operations are serialized
within a JavaScript realm for the same endpoint URL or shared transport object.
Capture requires Kakapo's capture_screenshot RPC: [[token], nativeCamera], or [[token]]
to use the active camera when the request reaches the front of the queue. Explicit camera inputs
are converted by the SDK; omitted lens fields are preserved from its pre-request camera read.
Kakapo queues captures across connections, applies each camera without animation, renders with
that request's camera, and freezes the pixels before processing the next capture. The response is
a standard token-correlated JPEG, not camera metadata for the SDK to poll. There is no settling
delay or frame retry loop. Concurrent editor input can change the active camera afterwards but
cannot replace the camera used by an explicit capture. Scene edits are not isolated by this queue.
The queue holds at most 64 pending captures. AI-diffusion-composited output returns a token error
rather than an unrelated image. Existing buffer RPCs remain available to other renderer clients.
All public inputs are checked at runtime as well as by TypeScript. Invalid calls throw structured
KakapoValidationError objects with a stable code, offending path, expected rule, and a
correction hint intended for automated agents.
accessScene(callback, { save?: boolean }) fetches the scene once and creates one editable draft
from an immutable baseline. The callback can inspect summaries, detailed properties and materials,
and apply mutations. Incoming JSON is parsed without full-scene validation. Draft operations retain
local checks. After the callback the draft is diffed once against the baseline; an empty diff is not
validated and does not patch or save. Otherwise the whole tree is checked structurally (root, parent
and child membership, cycles, Union ancestry), node values are checked only for nodes the draft
changed (their node or ext entry, their parent, or a material or script they reference), and one
patch is sent. Untouched nodes are not revalidated, so a pre-existing invalid value blocks only
edits that touch that node. A node value failure names the node: path is nodes.<id>.<field>
and details holds { nodeId, name, kind }. Callback handles expire on exit.
The result contains the callback value, changed, save, and the acknowledged native scene for
host reconciliation without another fetch. Preparation failure discards the draft. Cached scene
state is invalidated on every exit; use a new access for subsequent inspection.
The host owns permissions, connection lifetime and suspension of local editor changes throughout
access, including read-only callbacks. Connect with { refreshScene: false } to avoid a redundant
initial fetch. Access does not hash, reread for conflicts, add patch test guards, refetch after errors,
or replay uncertain commits. It adds no cross-client atomicity guarantee.
editScene and editSceneAndSave are compatibility shortcuts to the same access implementation.
node(id, kind).save() stages properties in the draft; it does not submit a separate commit.
For incremental curve edits, use SceneEdit.updateCurvePoint(id, index, changes),
cloneCurvePoint(id, sourceIndex, insertionIndex), and deleteCurvePoint(id, index).
These retain native per-point fields unknown to this SDK. Replacing a node's entire
typed points array replaces that data; use the granular operations when editing existing points.
Engine-backed reads such as bounding boxes and screenshots remain asynchronous. Run them before the
first draft mutation or after accessScene() commits.
Accesses are serialized per SDK client. Raw scene-mutating RPCs invalidate cached state; they are not coordinated by an access callback's host suspension.
Node IDs allocated by createNode() and cloneSubtree() increase for the lifetime of a KakapoAPI
instance. IDs observed in scene snapshots, draft patches, or numeric node-extension keys remain
reserved after deletion, rollback, and reconnect. This prevents delete-and-create edits from changing
an existing renderer object's type by reusing its ID. A new API instance starts above IDs in its current
snapshot; this is not a persistent project-wide allocator. Raw patches supply their own IDs and must
preserve object identity. IDs are not reserved across independent clients.
editSceneAndSave(callback) submits the standard scene_state_patch command and, after
acknowledgement, calls save_scene once on the same connection. No new Kakapo command is required.
The result preserves the callback value separately from save status: acknowledged includes the
returned filename, unknown includes an error, and not_needed means the draft did not change.
A save acknowledgement does not prove a new durable snapshot: some endpoints return an existing
filename. Verify persistence through the owning service. Never repeat the edit to recover saving.
The backend wrapper may apply geometry before extension processing fails; patch errors and lost
replies therefore have an unknown outcome unless rejection before execution is proven. SDK state
is invalidated after submission, and no mutation is automatically replayed.
Mesh, field, and decal names are syntax-checked, but their existence cannot be enumerated by the
current engine RPC surface. Font family/weight/style combinations are checked against listFonts().
Verification
yarn test # fake WebSocket protocol and API behavior
yarn test:live # launches the sibling Kakapo build and runs named live tests per API method
yarn test:all # runs both suitesThe live suite starts Kakapo once, runs methods sequentially, and reports failures as names such as
live:setNodeParent or live:createNode:mesh. Mutations refresh the authoritative engine scene
before asserting their result. See TEST_MATRIX.md for the exact unit/live mapping.
Build the sibling engine before running live tests. On Windows the default executable is
../kakapo/build/code/RelWithDebInfo/kakapo_app.exe; on other platforms it is
../kakapo/build/code/kakapo_app. Set KAKAPO_BINARY to test another build explicitly.
Importing catalog materials
Inside editScene, scene.importMaterial({ data, shaders }) imports native renderer material data and shader slots. data retains opaque fields and constants. Each non-null shader gets a new scene-local ID; null slots remain zero. The returned material can be assigned with setNodeMaterial. Material and shader resources share the draft's commit and rollback boundary. The host supplies the catalog data; the SDK does not fetch account or asset services.
