Skip to content

Commit 66eed40

Browse files
author
Cheems
committed
feat(media): route Open Design Cloud generation through Vela
Add Vela image and video adapters behind od media generate with trusted workspace attribution, multi-image support, video polling, and fail-closed behavior. Expose AMR-specific media defaults and pin the packaged Vela CLI used by the integration.
1 parent f2a7568 commit 66eed40

18 files changed

Lines changed: 1118 additions & 59 deletions

File tree

apps/daemon/src/cli.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,11 @@ async function runMediaGenerate(rawArgs) {
14741474
console.error('--model required (see http://<daemon>/api/media/models)');
14751475
process.exit(2);
14761476
}
1477+
const images = repeatableFlagValues(rawArgs, 'image');
1478+
if (flags.model.startsWith('vela/') && images.length > 5) {
1479+
console.error(`Vela media accepts at most 5 --image values; received ${images.length}`);
1480+
process.exit(2);
1481+
}
14771482

14781483
// Long-form media prompts (detailed image/video descriptions, program-
14791484
// generated prompts) arrive via --prompt-file <path|-> (stdin) per the CLI
@@ -1490,7 +1495,8 @@ async function runMediaGenerate(rawArgs) {
14901495
voice: flags.voice,
14911496
audioKind: flags['audio-kind'],
14921497
compositionDir: flags['composition-dir'],
1493-
image: flags.image,
1498+
image: images[0],
1499+
images,
14941500
language: flags.language,
14951501
};
14961502
if (flags.length != null) body.length = Number(flags.length);
@@ -1832,11 +1838,11 @@ Common options:
18321838
to the dir containing hyperframes.json /
18331839
meta.json / index.html. The daemon runs
18341840
\`npx hyperframes render\` against it.
1835-
--image <path> Project-relative path to a reference image
1836-
(image-to-video for Seedance i2v models, or
1837-
future image-edit endpoints). Daemon reads
1838-
the file from the project, base64-encodes
1839-
it, and forwards it to the upstream API.
1841+
--image <path> Project-relative reference image; repeat up to 5
1842+
times for Vela image editing or video references.
1843+
The first video image is the first frame; the rest
1844+
are references. Existing providers still receive
1845+
the first image through the legacy single-image field.
18401846
--daemon-url <url>
18411847
18421848
Output: a single line of JSON: {"file": { name, size, kind, mime, ... }}

apps/daemon/src/media/index.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import {
8080
fetchImageGenerationWithResponseRetry,
8181
type ImageGenerationRequestSummary,
8282
} from './image-generation-retry.js';
83+
import { renderVelaImage, renderVelaVideo } from './vela.js';
8384
import { codexNeedsDangerFullAccessSandbox } from '../runtimes/defs/codex.js';
8485
import {
8586
ensureProject,
@@ -144,6 +145,7 @@ type MediaContext = {
144145
/** Additional reference images for multi-image i2v / style reference flows. */
145146
imageRefs: ImageRef[];
146147
projectRoot: string;
148+
workspaceId: string | undefined;
147149
onProviderRequestSettled:
148150
| ((summary: ImageGenerationRequestSummary & { providerId: string }) => void)
149151
| undefined;
@@ -326,6 +328,7 @@ export async function generateMedia(args: {
326328
prompt?: string; output?: string; aspect?: string; length?: number; duration?: number; voice?: string;
327329
audioKind?: AudioKind; language?: string; loop?: boolean; promptInfluence?: number;
328330
compositionDir?: string; image?: string; images?: string[]; onProgress?: ProgressFn; requestInit?: MediaRequestInit;
331+
workspaceId?: string;
329332
onProviderRequestSettled?: (summary: ImageGenerationRequestSummary & { providerId: string }) => void;
330333
}) {
331334
const {
@@ -347,6 +350,7 @@ export async function generateMedia(args: {
347350
compositionDir,
348351
image,
349352
requestInit,
353+
workspaceId,
350354
onProviderRequestSettled,
351355
} = args;
352356

@@ -431,7 +435,15 @@ export async function generateMedia(args: {
431435
// when stubs are swapped for paid integrations.
432436
const lengthClamp =
433437
surface === 'video'
434-
? clampWithWarning(length, VIDEO_LENGTHS_SEC, 'length')
438+
? def.provider === 'vela'
439+
? {
440+
value:
441+
typeof length === 'number' && Number.isFinite(length)
442+
? length
443+
: undefined,
444+
warning: null,
445+
}
446+
: clampWithWarning(length, VIDEO_LENGTHS_SEC, 'length')
435447
: { value: undefined, warning: null };
436448
const usesProviderSpecificAudioDuration =
437449
def.provider === 'elevenlabs'
@@ -510,6 +522,7 @@ export async function generateMedia(args: {
510522
requestInit: requestInit || {},
511523
imageRefs,
512524
projectRoot,
525+
workspaceId,
513526
onProviderRequestSettled,
514527
};
515528

@@ -572,6 +585,19 @@ export async function generateMedia(args: {
572585
bytes = result.bytes;
573586
providerNote = result.providerNote;
574587
suggestedExt = result.suggestedExt;
588+
} else if (def.provider === 'vela' && surface === 'image') {
589+
const result = await renderVelaImage(ctx);
590+
bytes = result.bytes;
591+
providerNote = result.providerNote;
592+
suggestedExt = result.suggestedExt;
593+
} else if (def.provider === 'vela' && surface === 'video') {
594+
const result = await renderVelaVideo({
595+
...ctx,
596+
onProgress: args.onProgress,
597+
});
598+
bytes = result.bytes;
599+
providerNote = result.providerNote;
600+
suggestedExt = result.suggestedExt;
575601
} else if (def.provider === 'openai' && surface === 'image') {
576602
const result = await renderOpenAIImage(ctx, credentials);
577603
bytes = result.bytes;
@@ -765,7 +791,7 @@ export async function generateMedia(args: {
765791
// HyperFrames is a local render, not a remote provider. Falling back
766792
// to a stub here hides actionable composition/preflight failures and
767793
// can make the agent retry or narrate a fake MP4 as success.
768-
if (def.provider === 'hyperframes') {
794+
if (def.provider === 'hyperframes' || def.provider === 'vela') {
769795
throw err;
770796
}
771797
// A real provider failed (network blip, 4xx, missing key, …). We

apps/daemon/src/media/models.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type MediaModel = {
3333
export const MEDIA_PROVIDERS: MediaProvider[] = [
3434
{ id: 'openai', label: 'OpenAI', hint: 'gpt-image-2 / dall-e-3', integrated: true, defaultBaseUrl: 'https://api.openai.com/v1' },
3535
{ id: 'codex', label: 'Codex Subscription', hint: 'gpt-image-2 via local Codex CLI login', integrated: true, credentialsRequired: false, docsUrl: 'https://developers.openai.com/codex' },
36+
{ id: 'vela', label: 'Open Design Cloud', hint: 'Managed image and video generation through Vela', integrated: true, credentialsRequired: false, settingsVisible: false },
3637
{ id: 'volcengine', label: 'Volcengine Ark (Doubao)', hint: 'Seedance 2.0 / Seedream', integrated: true, defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3' },
3738
{ id: 'grok', label: 'xAI Grok Imagine', hint: 'grok-imagine — image + video with native audio', integrated: true, defaultBaseUrl: 'https://api.x.ai/v1' },
3839
{ id: 'hyperframes', label: 'HyperFrames', hint: 'Local HTML -> MP4 renderer', integrated: true, credentialsRequired: false, settingsVisible: false },
@@ -85,6 +86,11 @@ export const MEDIA_PROVIDERS: MediaProvider[] = [
8586
];
8687

8788
export const IMAGE_MODELS: MediaModel[] = [
89+
{ id: 'vela/gpt-image-2', label: 'gpt-image-2 (Cloud)', hint: 'Open Design Cloud · managed image generation and editing', provider: 'vela', caps: ['t2i', 'i2i'] },
90+
{ id: 'vela/nano-banana-2', label: 'nano-banana-2 (Cloud)', hint: 'Open Design Cloud · managed image generation and editing', provider: 'vela', caps: ['t2i', 'i2i'] },
91+
{ id: 'vela/nano-banana-2-lite', label: 'nano-banana-2-lite (Cloud)', hint: 'Open Design Cloud · fast managed image generation and editing', provider: 'vela', caps: ['t2i', 'i2i'] },
92+
{ id: 'vela/seedream-5.0', label: 'seedream-5.0 (Cloud)', hint: 'Open Design Cloud · managed image generation and editing', provider: 'vela', caps: ['t2i', 'i2i'] },
93+
{ id: 'vela/seedream-5.0-pro', label: 'seedream-5.0-pro (Cloud)', hint: 'Open Design Cloud · high-quality managed image generation and editing', provider: 'vela', caps: ['t2i', 'i2i'] },
8894
{ id: 'gpt-image-2', label: 'gpt-image-2', hint: 'OpenAI · 4K, native multimodal', provider: 'openai', caps: ['t2i', 'i2i', 'inpaint'], default: true },
8995
{ id: 'gpt-image-1.5', label: 'gpt-image-1.5', hint: 'OpenAI · 4× faster than gpt-image-1', provider: 'openai', caps: ['t2i', 'i2i', 'inpaint'] },
9096
{ id: 'gpt-image-1', label: 'gpt-image-1', hint: 'OpenAI · ChatGPT native', provider: 'openai', caps: ['t2i', 'i2i', 'inpaint'] },
@@ -152,6 +158,7 @@ export const IMAGE_MODELS: MediaModel[] = [
152158
];
153159

154160
export const VIDEO_MODELS: MediaModel[] = [
161+
{ id: 'vela/doubao-seedance-2-0-260128', label: 'seedance-2.0 (Cloud)', hint: 'Open Design Cloud · managed text/image-to-video · 720p default', provider: 'vela', caps: ['t2v', 'i2v'] },
155162
{ id: 'doubao-seedance-2-0-260128', label: 'seedance-2.0', hint: 'ByteDance · t2v + i2v + audio', provider: 'volcengine', caps: ['t2v', 'i2v', 'audio'], default: true },
156163
{ id: 'doubao-seedance-2-0-fast-260128', label: 'seedance-2.0-fast', hint: 'ByteDance · faster, cheaper', provider: 'volcengine', caps: ['t2v', 'i2v', 'audio'] },
157164
{ id: 'doubao-seedance-1-0-pro-250528', label: 'seedance-1.0-pro', hint: 'ByteDance · 1.0', provider: 'volcengine', caps: ['t2v', 'i2v'] },

0 commit comments

Comments
 (0)