Navigation: docs/developer/index.md → Reve provider guide
Audience: coding agents and contributors adding new Reve image nodes or model version strings to the
@nodetool-ai/reve-nodespackage.
TL;DR
- Add a new
classinpackages/reve-nodes/src/nodes/extendingBaseNode. - Export it from
packages/reve-nodes/src/index.tsand push the class intoREVE_NODES. - Call
reveGeneratefromreve-base.tswith one of the three API endpoints:"create","edit", or"remix". npm run build:packages— compiles fromsrc/todist/(the package loads fromdist/).npm run typecheck && npm run lint— must pass before committing.
Where things live
| What | Path |
|---|---|
| Shared API utilities | packages/reve-nodes/src/reve-base.ts |
| Create Image node | packages/reve-nodes/src/nodes/create-image.ts |
| Edit Image node | packages/reve-nodes/src/nodes/edit-image.ts |
| Remix Image node | packages/reve-nodes/src/nodes/remix-image.ts |
| Package entry point | packages/reve-nodes/src/index.ts |
| Runtime provider (generic image picker) | packages/runtime/src/providers/reve-provider.ts |
| Provider registration | packages/runtime/src/providers/index.ts (registerBuiltinProvider(PROVIDER_IDS.REVE, ...)) |
PROVIDER_IDS.REVE |
packages/protocol/src/api-types.ts |
| Unit tests | packages/reve-nodes/tests/nodes.test.ts, reve-base.test.ts |
| Registration test | packages/reve-nodes/tests/registration.test.ts |
How Reve models and nodes are defined
Reve exposes three POST endpoints, each mapping to one TypeScript class:
| Endpoint | Class | Node type |
|---|---|---|
POST /v1/image/create |
CreateImageNode |
reve.CreateImage |
POST /v1/image/edit |
EditImageNode |
reve.EditImage |
POST /v1/image/remix |
RemixImageNode |
reve.RemixImage |
There is no manifest or code-generation script. Each node is a hand-written BaseNode subclass in its own file under packages/reve-nodes/src/nodes/. Fields are declared with the @prop decorator from @nodetool-ai/node-sdk.
Shared utilities in reve-base.ts:
REVE_ASPECT_RATIOS— the aspect ratios all endpoints accept.REVE_POSTPROCESSING— optional post-generation operations (upscale,remove_background,fit_image,effect).getReveApiKey(secrets)— readsREVE_API_KEYfromthis._secretsthenprocess.env, throws if absent.reveGenerate(apiKey, endpoint, body)— POSTs tohttps://api.reve.com/v1/image/{endpoint}withAuthorization: Bearer, returns the parsedReveImageResponse.refToBase64(ref, context)— converts anImageRef-like value to a base64 string; handles inline data, data URIs,asset://,file://, and remote URLs.reveImageToRef(base64)— wraps the responseimagefield into a NodeToolImageRefobject; attaches dimensions viasharpwhen available.
The runtime provider (reve-provider.ts) separately implements textToImage and imageToImage for the generic image-generation picker in the UI (the two hardcoded model ids reve-create and reve-edit). This is a thinner path — new Reve API endpoints should be added as full nodes in reve-nodes, not as branches in the provider.
Add a new model version
To add a new dated model version pin (e.g. reve-create@20260101), open the relevant node file and extend the version prop’s values array:
// packages/reve-nodes/src/nodes/create-image.ts
@prop({
type: "enum",
default: "latest",
title: "Version",
description: "Model version to use.",
values: ["latest", "reve-create@20250915", "reve-create@20260101"] // ← add here
})
declare version: any;
That is the entire change. Rebuild and verify (see Verify below).
Add a new node
Use this pattern when Reve releases a new endpoint (or a sufficiently different variant that warrants its own node type).
Step 1. Create packages/reve-nodes/src/nodes/your-endpoint.ts:
import { BaseNode, prop } from "@nodetool-ai/node-sdk";
import type { NodeClass } from "@nodetool-ai/node-sdk";
import {
getReveApiKey,
postprocessingArray,
reveGenerate,
reveImageToRef,
REVE_ASPECT_RATIOS,
REVE_POSTPROCESSING
} from "../reve-base.js";
export class YourEndpointNode extends BaseNode {
static readonly nodeType = "reve.YourEndpoint";
static readonly body = "content_card";
static readonly title = "Your Endpoint";
static readonly description =
"One-line description of what this node does.\n" +
"image, reve, keyword1, keyword2\n\n" +
"Use cases:\n" +
"- First use case\n" +
"- Second use case";
static readonly metadataOutputTypes = { output: "image" };
static readonly inlineFields: string[] = ["prompt"];
static readonly requiredSettings = ["REVE_API_KEY"];
static readonly autoSaveAsset = true;
@prop({
type: "str",
default: "",
title: "Prompt",
description: "Text prompt (max 2560 chars)."
})
declare prompt: any;
@prop({
type: "enum",
default: "3:2",
title: "Aspect Ratio",
description: "Proportions of the generated image.",
values: [...REVE_ASPECT_RATIOS]
})
declare aspect_ratio: any;
@prop({
type: "enum",
default: "latest",
title: "Version",
description: "Model version to use.",
values: ["latest"]
})
declare version: any;
@prop({
type: "enum",
default: "none",
title: "Postprocessing",
description: "Optional postprocessing operation applied to the result.",
values: [...REVE_POSTPROCESSING]
})
declare postprocessing: any;
@prop({
type: "int",
default: 1,
title: "Test Time Scaling",
description: "Effort multiplier for quality (1-15). Higher costs more.",
min: 1,
max: 15
})
declare test_time_scaling: any;
async process(): Promise<Record<string, unknown>> {
const apiKey = getReveApiKey(this._secrets);
const prompt = String(this.prompt ?? "");
if (!prompt) throw new Error("Prompt is required");
const result = await reveGenerate(apiKey, "create", { // ← swap endpoint name
prompt,
aspect_ratio: String(this.aspect_ratio ?? "3:2"),
version: String(this.version ?? "latest"),
postprocessing: postprocessingArray(this.postprocessing),
test_time_scaling: Number(this.test_time_scaling ?? 1)
});
return { output: await reveImageToRef(result.image) };
}
}
export const YOUR_ENDPOINT_NODES: readonly NodeClass[] = [YourEndpointNode];
Nodes that accept a reference image (like EditImageNode) must take a context argument in process and pass it to refToBase64:
async process(
context?: Parameters<BaseNode["process"]>[0]
): Promise<Record<string, unknown>> {
const referenceImage = await refToBase64(this.image, context);
// ...
}
Step 2. Wire the new class into packages/reve-nodes/src/index.ts:
import { YOUR_ENDPOINT_NODES } from "./nodes/your-endpoint.js";
export { YourEndpointNode } from "./nodes/your-endpoint.js";
export const REVE_NODES: readonly NodeClass[] = [
...CREATE_IMAGE_NODES,
...EDIT_IMAGE_NODES,
...REMIX_IMAGE_NODES,
...YOUR_ENDPOINT_NODES // ← add here
];
No other files need to change. The node automatically appears in the registry because registerReveNodes iterates REVE_NODES.
Step 3. Add tests in packages/reve-nodes/tests/nodes.test.ts. Follow the existing pattern: stub fetch, construct the node with props, call process(), assert the fetch URL and body fields.
Verify
Run these commands in order after any change:
# 1. Compile src/ → dist/ (required — the package loads from dist/)
npm run build:packages
# 2. Type-check the whole repo
npm run typecheck
# 3. Lint
npm run lint
# 4. Run the reve-nodes unit tests
npm run test --workspace=packages/reve-nodes
# 5. Confirm the new node type resolves in the CLI registry
# Expected: a REVE_API_KEY error, not "unknown node type"
npm run dev:nodetool -- node run reve.YourEndpoint \
--props '{"prompt":"a red fox"}' \
--no-secrets
# 6. Static validation (no API key required)
npm run dev:nodetool -- validate workflow.json
# 7. Affected tests and the harness gate
npm run test:affected
npm run dev:nodetool -- harness gate --base origin/main
A REVE_API_KEY is not configured error from step 5 means the node registered correctly — it loaded from dist/ and resolved the type. An unknown node type error means the build or export wiring is broken.
Contributing
Source: https://github.com/nodetool-ai/nodetool
Discord: https://discord.gg/WmQTWZRcYE
Before opening a PR, run the Verify steps. Add a test for every new node; the existing nodes.test.ts stubs fetch globally, so new test cases fit cleanly in the same file.