A lightweight multimodal generation SDK with built-in model presets, model declaration files, and adapter-based provider calls.
npm install @neta-art/generationimport { createGenerationClient } from "@neta-art/generation";
const client = createGenerationClient({
apiKey: process.env.NETA_ROUTER_API_KEY!,
});
const output = await client.generate({
model: "gpt-image-2",
content: [
{ type: "text", text: "a cinematic portrait of a robot florist, 35mm film" },
],
parameters: {
size: "1024x1024",
quality: "high",
},
});
console.log(output);baseUrl defaults to https://router.neta.art. Pass a different endpoint when needed:
const client = createGenerationClient({
apiKey: process.env.NETA_ROUTER_API_KEY!,
baseUrl: "https://router.neta.art",
});.env is ignored by Git. Copy .env.example to .env and fill in your router key:
cp .env.example .envNETA_ROUTER_API_KEY=your_api_key_hereNode.js does not load .env automatically for library code. The example scripts use Node's native --env-file flag through npm scripts:
pnpm example:basic-image
pnpm example:image-editing
pnpm example:text-to-videoYou can also call providers through the CLI:
node --env-file=.env ./dist/cli/index.js generate gemini-3.1-flash-image-preview \
--prompt "a simple abstract geometric app icon" \
--param aspect_ratio=1:1 \
--param image_size=512 \
--debugUse --image-url for reference images, --out to write base64 outputs to files, and json: for non-string parameter values, for example --param duration=json:5.
Pass debug: true to print the final provider request and response metadata to stderr. Sensitive fields such as Authorization and base64 image data are redacted by default.
const client = createGenerationClient({
apiKey: process.env.NETA_ROUTER_API_KEY!,
debug: true,
});For a custom logger or full unredacted payloads:
const client = createGenerationClient({
apiKey: process.env.NETA_ROUTER_API_KEY!,
debug: {
enabled: true,
includeSensitive: true,
logger: (event) => console.error(JSON.stringify(event, null, 2)),
},
});gpt-image-2gemini-3.1-flash-image-previewseedance-2-0seedance-2-0-fast
Built-in model declarations share the same client-level apiKey and baseUrl.
const output = await client.generate({
model: "gemini-3.1-flash-image-preview",
content: [
{ type: "text", text: "turn this portrait into a watercolor illustration" },
{ type: "image", source: { type: "url", url: "https://example.com/portrait.jpg" } },
],
parameters: {
aspect_ratio: "3:4",
image_size: "2K",
},
});const output = await client.generate({
model: "seedance-2-0-fast",
content: [
{ type: "text", text: "a cat playing piano in a cozy jazz club, cinematic lighting" },
],
parameters: {
duration: 5,
resolution: "720p",
aspect_ratio: "16:9",
},
});Frame and reference-image video modes use meta.role:
await client.generate({
model: "seedance-2-0",
content: [
{ type: "text", text: "create a smooth dramatic transition" },
{ type: "image", source: { type: "url", url: "https://example.com/start.jpg" }, meta: { role: "first_frame" } },
{ type: "image", source: { type: "url", url: "https://example.com/end.jpg" }, meta: { role: "last_frame" } },
],
});import { createGenerationClientFromDirectory } from "@neta-art/generation";
const client = await createGenerationClientFromDirectory("./models", {
apiKey: process.env.NETA_ROUTER_API_KEY!,
});Supported declaration formats:
.yaml.yml.json
Custom declarations are merged with built-in models by default. If the same model exists, the custom declaration wins.
import { exportBuiltinModelConfig } from "@neta-art/generation";
await exportBuiltinModelConfig("gpt-image-2", "./gpt-image-2.yaml");CLI:
neta-generation models list
neta-generation models export gpt-image-2 --out ./gpt-image-2.yaml
neta-generation models export-all --out ./modelsschema: neta.generation.model.v1
model: gpt-image-2
title: GPT Image 2
adapter:
type: openai.images
content:
input:
- type: text
required: true
min: 1
max: 16
merge: newline
- type: image
required: false
max: 16
sources:
- url
- base64
parameters:
size:
type: string
optional: true
default: 1024x1024Adapter credentials are intentionally not stored in model declarations. Use client-level or request-level apiKey and baseUrl instead.
type GenerationSource =
| { type: "url"; url: string }
| { type: "base64"; mediaType: string; data: string };Built-in adapters:
openai.imagesgemini.generateContentark.videoGenerations
You can register custom adapters:
const client = createGenerationClient({
apiKey,
adapters: {
"custom.adapter": async (input) => {
return [];
},
},
});const resolved = client.validate({
model: "gpt-image-2",
content: [{ type: "text", text: "hello" }],
});
console.log(resolved.parameters);import { GenerationValidationError, GenerationProviderError } from "@neta-art/generation";
try {
await client.generate(request);
} catch (error) {
if (error instanceof GenerationValidationError) {
console.error("Invalid request", error.message);
} else if (error instanceof GenerationProviderError) {
console.error("Provider failed", error.message);
}
}