/** * route-prerequisites.ts — the ONE canonical declaration of what each provider * route needs before it can run, and which specialist lanes it belongs to. * * This is a LEAF module on purpose: it imports only `./types.js`, so anything * may depend on it without dragging in `provider-status.ts` → * `execution-runtime.ts` (and from there most of the domain). `provider-status.ts` * used to hold five parallel `Record` tables that a new route * had to be added to five times; they live here now and it reads them. * * The lane flags are DECLARED, not inferred. `batchQueue` and `voiceRefInjection` * are deliberate specialist subsets — a route being live does not make it a batch * route, and does not mean a voice clone should be injected into its payload — so * each one names its members here and the consumers derive their sets from these * flags. `capability-contract.test.ts` fails when a consumer drifts. */ import type { ProviderRouteId } from './types.js'; /** * Executables a route's native transport needs on PATH. Kept as a local literal * union rather than an import so this module stays a leaf; it is structurally * assignable to `provider-status.ts`'s `ExecutableName`. */ export type RouteDependencyName = 'python3' | 'bun' | 'ffmpeg'; /** Everything a route needs configured, plus the specialist lanes it belongs to. */ export interface RoutePrerequisites { /** Env vars the route's built-in transport needs, in report order. */ requiredEnvVars: string[]; /** Executables the route's native transport shells out to. */ requiredDependencies: RouteDependencyName[]; /** `scaffold` routes report as `degraded` and stay out of default routing. */ maturity: 'production' | 'scaffold'; /** Env var naming a full custom adapter binary for this route. */ adapterEnvVar: string; /** Env vars naming per-route command shims run through the built-in adapter. */ commandEnvVars: string[]; /** Declared membership of the specialist lanes that use a route subset. */ lanes: { /** * The overnight batch queue (`batch-queue.ts`) targets only the routes with * in-process native submit/poll transports that loop `payload.tasks`. */ batchQueue: boolean; /** * Routes that consume a blank-video-with-audio voice clone as a video * reference to lock a cloned voice (the black-frame video carries the voice; * a raw MP3 does NOT lock it as reliably — that is the whole point of the * trick): * • seedance-direct — the .mp4 rides into `reference_videos` * • runway-useapi — Seedance-2 via the UseAPI gateway → `videoAssetId` + audio:true * • dreamina-useapi — Seedance-2 via Dreamina Omni Reference → `omni_N_videoRef` * (the omni video ref is EXPECTED to drive the voice on its own; unverified * live — if a render is mute, VCLAW_DREAMINA_AUDIO=1 forces `audio:true`) * Other routes leave voice clones un-injected (byte-identical). */ voiceRefInjection: boolean; }; } /** * Prerequisites for every live provider route. The key set is asserted equal to * `PROVIDER_ROUTE_IDS` by `capability-contract.test.ts`, so a new route cannot * be added to the id tuple without landing here too. */ export const ROUTE_PREREQUISITES: Record = { 'veo-useapi': { requiredEnvVars: ['USEAPI_API_TOKEN', 'USEAPI_ACCOUNT_EMAIL'], requiredDependencies: ['python3', 'bun', 'ffmpeg'], maturity: 'production', adapterEnvVar: 'VCLAW_VEO_USEAPI_ADAPTER', commandEnvVars: ['VCLAW_VEO_USEAPI_SUBMIT_CMD'], lanes: { batchQueue: false, voiceRefInjection: false }, }, 'runway-useapi': { requiredEnvVars: ['USEAPI_API_TOKEN', 'USEAPI_ACCOUNT_EMAIL'], // runway-useapi native transport is pure Node (fetch + fs), no python/bun // shell-outs. ffmpeg is still useful for downstream stitching/post but is // not strictly required for the route itself to deliver mp4s. requiredDependencies: ['ffmpeg'], maturity: 'production', adapterEnvVar: 'VCLAW_RUNWAY_USEAPI_ADAPTER', commandEnvVars: ['VCLAW_RUNWAY_USEAPI_SUBMIT_CMD'], lanes: { batchQueue: true, voiceRefInjection: true }, }, 'dreamina-useapi': { requiredEnvVars: ['USEAPI_API_TOKEN', 'VCLAW_DREAMINA_ACCOUNT'], // dreamina-useapi native transport is pure Node (fetch + fs), no python/bun // shell-outs. ffmpeg is still useful for downstream stitching/post but is // not strictly required for the route itself to deliver mp4s. requiredDependencies: ['ffmpeg'], maturity: 'production', adapterEnvVar: 'VCLAW_DREAMINA_USEAPI_ADAPTER', commandEnvVars: ['VCLAW_DREAMINA_USEAPI_SUBMIT_CMD'], lanes: { batchQueue: true, voiceRefInjection: true }, }, 'seedance-direct': { requiredEnvVars: ['SUTUI_API_KEY'], requiredDependencies: ['python3', 'ffmpeg'], maturity: 'production', adapterEnvVar: 'VCLAW_SEEDANCE_DIRECT_ADAPTER', commandEnvVars: ['VCLAW_SEEDANCE_DIRECT_SUBMIT_CMD'], lanes: { batchQueue: true, voiceRefInjection: true }, }, 'magnific-rest': { requiredEnvVars: ['MAGNIFIC_API_KEY'], // magnific-rest native transport is pure Node (fetch + fs). ffmpeg only for // downstream stitching/post, not strictly required for the route itself. requiredDependencies: ['ffmpeg'], maturity: 'production', adapterEnvVar: 'VCLAW_MAGNIFIC_REST_ADAPTER', commandEnvVars: ['VCLAW_MAGNIFIC_REST_SUBMIT_CMD'], lanes: { batchQueue: false, voiceRefInjection: false }, }, }; /** Every route id whose declared `lanes[lane]` flag is true, in declaration order. */ export function routesInLane(lane: keyof RoutePrerequisites['lanes']): ProviderRouteId[] { return (Object.keys(ROUTE_PREREQUISITES) as ProviderRouteId[]).filter( (routeId) => ROUTE_PREREQUISITES[routeId].lanes[lane], ); }