Library API

Provider integrations

Provider is the capability boundary. Generation pipelines accept that interface rather than importing PixelLab directly. PixelLabProvider, RetroDiffusionProvider, ComfyUIProvider, and ScenarioProvider are built in; FakeProvider implements it in memory for deterministic tests.

import { FakeProvider, fetchAssets, poll, submit } from "pixelkiln"

const provider = new FakeProvider({ candidates: 4 })
const lockPath = "pixelkiln.lock.json"
await submit(provider, loaded, plan.actionable, lock, lockPath)
await poll(provider, lock, lockPath, { specs })
await fetchAssets(provider, specs, lock, lockPath)

Provider-backed operations mutate the supplied lock object; persist at the workflow boundary with saveLock. See provider comparison before selecting or implementing another backend, especially its optional capabilities and cost units.

Custom providers may implement resolveInputs(inputs, context) when assets can supply provider-owned values. Return inputs for runtime validation/submission and an optional JSON-safe identity for specHash. The identity must include every byte or scalar that can change provider output and must exclude machine-local paths or credentials. Providers that omit the hook fail closed when an asset declares non-empty providerInputs.

Provider.submit receives an optional third SubmitContext argument. A custom provider that needs several remote mutations for one asset should call await context.checkpoint({ jobId, metadata, complete }) after each accepted mutation and before starting the next one. PixelKiln persists that checkpoint atomically. On an unchanged retry, previousJobId and previousMetadata let the provider continue from the first missing request. Set complete: true only when the returned job id is safe to poll as the complete asset.

These low-level operations intentionally accept one provider. A mixed-provider caller should partition specs and plan items by spec.provider, instantiate each adapter independently, and preserve the provider recorded on a lock entry when resuming work. The CLI is the reference orchestration and validates every provider-keyed budget before the first submission.

Every built-in adapter sends its HTTP through fetchWithRetry, exported for custom providers. It retries transport failures, 408, 429, 500, 502, 503, and 504 up to MAX_RETRIES (4) more times, waits on Retry-After when the server sends one and on exponential backoff with jitter otherwise, and gives each attempt its own 120-second timeout unless the caller passes a signal. An abort the caller asked for is never retried. Pass { retries: 0 } on a call that must answer fast; the built-in connectivity checks behind doctor do.

import { fetchWithRetry } from "pixelkiln"

const http = fetchWithRetry(undefined, { timeoutMs: 30_000 })
const res = await http("https://api.example.test/jobs", { method: "POST", body })

isSensitiveSourceUrl detects credential-bearing provider URLs, while shouldPersistSourceUrl applies the lockfile rule: keep durable public or provider-specific references, but drop signed URLs, inline data, and local file URLs after successful ingestion. Adapters should prefer refreshable references such as retrodiffusion:// and comfyui:// over temporary download URLs. Scenario uses the same rule with scenario:// asset references.

submit validates adapter estimates again at the spending boundary and returns { spent, unit } for successful submissions. Lock entries retain fractional costs with their unit; use spendByUnit(lock) for history. Use measureBalanceChange(before, after) when the provider exposes authoritative balance readings and keep that observed delta distinct from the estimate.