videobook 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +3 -2
  3. package/dist/command-reference.js +16 -2
  4. package/dist/env-secrets.js +6 -4
  5. package/dist/jobs/complete-provider-task.js +7 -1
  6. package/dist/jobs/generate-media.js +4 -0
  7. package/dist/jobs/render-project.js +124 -31
  8. package/dist/media/adapters/index.js +4 -0
  9. package/dist/media/adapters/machgen.js +48 -0
  10. package/dist/media/adapters/types.js +1 -0
  11. package/dist/media/catalog.js +70 -0
  12. package/dist/media/complete.js +1 -1
  13. package/dist/media/frontier-gaps.js +49 -0
  14. package/dist/media/frontier.js +30 -0
  15. package/dist/media/listing.js +9 -2
  16. package/dist/media/machgen.js +261 -0
  17. package/dist/media/model-id.js +1 -1
  18. package/dist/media/preview.js +2 -0
  19. package/dist/media/prompt-dialects.js +2 -0
  20. package/dist/media/seedance-draft.js +48 -0
  21. package/dist/media/wire.js +3 -0
  22. package/dist/notebook-action-catalog.js +5 -0
  23. package/dist/one-password.js +13 -7
  24. package/dist/provider-status-link.js +1 -1
  25. package/dist/provider-task-status.js +6 -3
  26. package/dist/provider-usage.js +5 -3
  27. package/dist/queue.js +3 -0
  28. package/dist/tools/commands.js +1 -1
  29. package/dist/tools/estimate-cost.js +3 -1
  30. package/dist/tools/provider-key-status.js +2 -0
  31. package/dist/tools/reconcile-project-work.js +69 -1
  32. package/dist/usage-providers.js +4 -0
  33. package/dist/workspace-action-catalog.js +1 -1
  34. package/package.json +14 -13
  35. package/ui/assets/{alive-captions-4QySEGdt.js → alive-captions-Bp6dxosy.js} +1 -1
  36. package/ui/assets/confirm-bodies-Ds1HtaGc.js +1 -0
  37. package/ui/assets/{fullscreen-CzTnPPG5.js → fullscreen-BCeVHjvY.js} +1 -1
  38. package/ui/assets/{goal-ui-CosrK_ko.js → goal-ui-DbAGwgMN.js} +2 -2
  39. package/ui/assets/index-D1d-F1y9.js +37 -0
  40. package/ui/assets/index-DWfR79lA.css +1 -0
  41. package/ui/assets/{inspector-BN2fezh4.js → inspector-BJTC0luH.js} +1 -1
  42. package/ui/assets/{jobs-ui-C4Wgy6I1.js → jobs-ui-C1cbw-xG.js} +1 -1
  43. package/ui/assets/{music-video-BSj8SgbQ.js → music-video-CWtxPlxq.js} +1 -1
  44. package/ui/assets/ui-CXDi5Omq.js +2 -0
  45. package/ui/assets/vendor-BFupLz37.js +19 -0
  46. package/ui/assets/{vendor-flow-zBavaNEW.js → vendor-flow-CKiuzaeM.js} +1 -1
  47. package/ui/assets/{vendor-mcp-DQ7EusT9.js → vendor-mcp-DQEeVZH0.js} +1 -1
  48. package/ui/assets/videobook-J-j9bSjM.js +20 -0
  49. package/ui/index.html +12 -12
  50. package/ui/assets/confirm-bodies-Bw818V9J.js +0 -1
  51. package/ui/assets/index-C9xjFEGL.js +0 -37
  52. package/ui/assets/index-DzPAnCUj.css +0 -1
  53. package/ui/assets/ui-Cpr6zMTA.js +0 -2
  54. package/ui/assets/vendor-yctIZ9Dz.js +0 -19
  55. package/ui/assets/videobook-B5G0jMoi.js +0 -20
@@ -1,5 +1,10 @@
1
+ import { modelFrontierRankings } from "./frontier.js";
2
+ import { PROVIDER_ENV_KEYS } from "./adapters/index.js";
1
3
  import { MODEL_ENTRIES } from "./catalog.js";
2
4
  import { resolveModel } from "./descriptor.js";
5
+ export function modelIsHidden(entry) {
6
+ return entry.hidden === true && modelFrontierRankings(entry.id).length === 0;
7
+ }
3
8
  export function modelListing(model) {
4
9
  return {
5
10
  id: model.id,
@@ -12,12 +17,14 @@ export function modelListing(model) {
12
17
  refs: model.slots.map(({ role, min, max }) => ({ role, min, max })),
13
18
  modes: model.modes,
14
19
  options: model.options,
15
- hidden: model.entry.hidden === true,
20
+ hidden: modelIsHidden(model.entry),
21
+ available: Boolean(process.env[PROVIDER_ENV_KEYS[model.provider]]?.trim()),
22
+ frontier: modelFrontierRankings(model.id),
16
23
  };
17
24
  }
18
25
  export function listModels(filter = {}) {
19
26
  return MODEL_ENTRIES
20
- .filter((entry) => filter.includeHidden || !entry.hidden)
27
+ .filter((entry) => filter.includeHidden || !modelIsHidden(entry))
21
28
  .filter((entry) => !filter.media || entry.media === filter.media)
22
29
  .filter((entry) => !filter.operation || entry.operations.includes(filter.operation))
23
30
  .map((entry) => resolveModel(entry.id));
@@ -0,0 +1,261 @@
1
+ import { z } from "zod";
2
+ import { MachGenGenerateRequestSchema } from "@apicity/machgen/zod";
3
+ const DEFAULT_DURATION_SECONDS = 5;
4
+ const SEEDANCE_ASPECT_RATIOS = ["adaptive", "21:9", "16:9", "9:16", "1:1", "4:3", "3:4"];
5
+ const ALL_REFERENCES = ["image", "video", "audio"];
6
+ export const MACHGEN_MODELS = {
7
+ "MiniMax-H3": {
8
+ taskTypes: ["T2V", "I2V", "R2V"],
9
+ resolutions: { "480p": 480, "768p": 768, "1440p": 1440 },
10
+ defaultResolution: "768p",
11
+ durations: { min: 1, max: 30, smart: false },
12
+ aspectRatios: ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"],
13
+ audioToggle: true,
14
+ seed: true,
15
+ lastFrame: "alone",
16
+ references: ALL_REFERENCES,
17
+ visualReference: false,
18
+ promptRequiredFor: ["T2V", "I2V", "R2V"],
19
+ maxPromptChars: 65535,
20
+ maxSecondsWithBothFrames: 15,
21
+ maxSecondsAt: { "1440p": 15 },
22
+ },
23
+ "Seedance-2.5": {
24
+ taskTypes: ["T2V", "I2V", "R2V"],
25
+ resolutions: { "480p": 480, "720p": 720, "1080p": 1080 },
26
+ defaultResolution: "720p",
27
+ durations: { min: 4, max: 30, smart: true },
28
+ aspectRatios: SEEDANCE_ASPECT_RATIOS,
29
+ audioToggle: true,
30
+ seed: false,
31
+ lastFrame: "with_first",
32
+ references: ALL_REFERENCES,
33
+ visualReference: false,
34
+ promptRequiredFor: ["T2V", "R2V"],
35
+ maxPromptChars: 25000,
36
+ smartEditNeedsAdaptive: true,
37
+ },
38
+ "Seedance-2.0": {
39
+ taskTypes: ["T2V", "I2V", "R2V"],
40
+ resolutions: { "480p": 480, "720p": 720, "1080p": 1080, "2160p": 2160 },
41
+ defaultResolution: "720p",
42
+ durations: { min: 4, max: 15, smart: true },
43
+ aspectRatios: SEEDANCE_ASPECT_RATIOS,
44
+ audioToggle: true,
45
+ seed: true,
46
+ lastFrame: "with_first",
47
+ references: ALL_REFERENCES,
48
+ visualReference: true,
49
+ promptRequiredFor: ["T2V"],
50
+ },
51
+ "Alibaba-Wan-3.0": {
52
+ taskTypes: ["T2V", "I2V", "R2V"],
53
+ resolutions: { "480p": 480, "720p": 720, "1080p": 1080 },
54
+ defaultResolution: "720p",
55
+ durations: { min: 2, max: 30, smart: true },
56
+ aspectRatios: [],
57
+ videoConfig: { aspect_ratio: "adaptive", fps: 30, audio: true },
58
+ audioToggle: false,
59
+ seed: true,
60
+ lastFrame: "with_first",
61
+ references: ALL_REFERENCES,
62
+ visualReference: false,
63
+ promptRequiredFor: ["T2V"],
64
+ },
65
+ "HappyHorse-1.1": {
66
+ taskTypes: ["T2V", "I2V", "R2V"],
67
+ resolutions: { "720p": 720, "1080p": 1080 },
68
+ defaultResolution: "720p",
69
+ durations: { min: 3, max: 15, smart: false },
70
+ aspectRatios: ["16:9", "9:16", "1:1", "4:3", "21:9"],
71
+ framesFollowImage: true,
72
+ audioToggle: false,
73
+ seed: true,
74
+ lastFrame: "none",
75
+ references: ["image"],
76
+ visualReference: true,
77
+ promptRequiredFor: ["T2V"],
78
+ },
79
+ "Grok-Imagine-Video-1.5": {
80
+ taskTypes: ["I2V"],
81
+ resolutions: { "480p": 480, "720p": 720, "1080p": 1080 },
82
+ defaultResolution: "720p",
83
+ durations: { min: 1, max: 15, smart: false },
84
+ aspectRatios: [],
85
+ audioToggle: false,
86
+ seed: false,
87
+ lastFrame: "none",
88
+ references: [],
89
+ visualReference: true,
90
+ promptRequiredFor: [],
91
+ },
92
+ };
93
+ const REFERENCE_FIELDS = {
94
+ image: "reference_image_urls",
95
+ video: "reference_video_urls",
96
+ audio: "reference_audio_urls",
97
+ };
98
+ const SOURCE_FIELDS = {
99
+ image: "src_image_urls",
100
+ video: "src_video_urls",
101
+ audio: "src_audio_urls",
102
+ };
103
+ function text(value) {
104
+ return typeof value === "string" && value.trim() !== "" ? value : null;
105
+ }
106
+ function list(value) {
107
+ return Array.isArray(value) ? value.filter((item) => text(item) !== null) : [];
108
+ }
109
+ function referencesOf(spec, value) {
110
+ const found = { image: [], video: [], audio: [] };
111
+ for (const kind of spec.references)
112
+ found[kind] = list(value[REFERENCE_FIELDS[kind]]);
113
+ return found;
114
+ }
115
+ function hasFrames(value) {
116
+ return text(value.first_frame_url) !== null || text(value.last_frame_url) !== null;
117
+ }
118
+ function taskTypeOf(spec, value) {
119
+ if (hasFrames(value))
120
+ return "I2V";
121
+ const references = referencesOf(spec, value);
122
+ return ALL_REFERENCES.some((kind) => references[kind].length > 0) ? "R2V" : "T2V";
123
+ }
124
+ function promptField(spec) {
125
+ const prompt = spec.maxPromptChars === undefined ? z.string() : z.string().max(spec.maxPromptChars);
126
+ return spec.taskTypes.every((task) => spec.promptRequiredFor.includes(task)) ? prompt.min(1) : prompt.optional();
127
+ }
128
+ function durationField(spec) {
129
+ const { min, max, smart } = spec.durations;
130
+ const seconds = z.number().int().min(min).max(max);
131
+ return (smart ? z.union([z.literal(-1), seconds]) : seconds)
132
+ .default(DEFAULT_DURATION_SECONDS)
133
+ .describe(smart ? `Seconds (${min}-${max}); -1 lets the model choose the length` : `Seconds (${min}-${max})`);
134
+ }
135
+ function frameFields(spec) {
136
+ if (!spec.taskTypes.includes("I2V"))
137
+ return {};
138
+ const first = z.string().min(1);
139
+ return {
140
+ first_frame_url: spec.taskTypes.length === 1 ? first : first.optional(),
141
+ ...(spec.lastFrame === "none" ? {} : { last_frame_url: z.string().min(1).optional() }),
142
+ };
143
+ }
144
+ function referenceFields(spec) {
145
+ const source = MachGenGenerateRequestSchema.shape;
146
+ return Object.fromEntries(spec.references.map((kind) => [REFERENCE_FIELDS[kind], source[SOURCE_FIELDS[kind]]]));
147
+ }
148
+ function settingFields(spec) {
149
+ const [firstAspect, ...otherAspects] = spec.aspectRatios;
150
+ return {
151
+ duration: durationField(spec),
152
+ resolution: z.enum(Object.keys(spec.resolutions))
153
+ .default(spec.defaultResolution)
154
+ .describe("Output resolution"),
155
+ ...(firstAspect === undefined ? {} : {
156
+ aspect_ratio: z.enum([firstAspect, ...otherAspects]).default(firstAspect).describe("Output aspect ratio"),
157
+ }),
158
+ ...(spec.audioToggle ? { generate_audio: z.boolean().default(true).describe("Generate a soundtrack with the video") } : {}),
159
+ ...(spec.seed ? { seed: z.number().int().min(0).max(2_147_483_647).optional().describe("Seed for reproducible output") } : {}),
160
+ };
161
+ }
162
+ function addIssue(ctx, path, message) {
163
+ ctx.addIssue({ code: "custom", message, path: path ? [path] : [] });
164
+ }
165
+ function checkFrames(spec, value, ctx) {
166
+ if (!hasFrames(value))
167
+ return;
168
+ const references = referencesOf(spec, value);
169
+ for (const kind of ALL_REFERENCES) {
170
+ if (references[kind].length > 0)
171
+ addIssue(ctx, REFERENCE_FIELDS[kind], "cannot be combined with first_frame_url or last_frame_url");
172
+ }
173
+ if (text(value.first_frame_url) === null && spec.lastFrame !== "alone")
174
+ addIssue(ctx, "last_frame_url", "last_frame_url requires first_frame_url");
175
+ const both = text(value.first_frame_url) !== null && text(value.last_frame_url) !== null;
176
+ const limit = spec.maxSecondsWithBothFrames;
177
+ if (both && limit !== undefined && typeof value.duration === "number" && value.duration > limit) {
178
+ addIssue(ctx, "duration", `a first and last frame allow at most ${limit} seconds`);
179
+ }
180
+ }
181
+ function checkReferences(spec, value, task, ctx) {
182
+ if (task !== "R2V")
183
+ return;
184
+ const references = referencesOf(spec, value);
185
+ if (spec.visualReference && references.image.length === 0 && references.video.length === 0) {
186
+ addIssue(ctx, "", "reference-to-video needs at least one reference image or video");
187
+ }
188
+ if (spec.smartEditNeedsAdaptive && value.duration === -1 && references.video.length > 0 && value.aspect_ratio !== "adaptive") {
189
+ addIssue(ctx, "aspect_ratio", "-1 duration with a reference video edits that video and needs the adaptive aspect ratio");
190
+ }
191
+ }
192
+ function checkSettings(spec, value, task, ctx) {
193
+ if (!spec.taskTypes.includes(task))
194
+ addIssue(ctx, "", `${task} is not supported; use ${spec.taskTypes.join(", ")}`);
195
+ if (spec.promptRequiredFor.includes(task) && text(value.prompt) === null)
196
+ addIssue(ctx, "prompt", "Required");
197
+ const limit = typeof value.resolution === "string" ? spec.maxSecondsAt?.[value.resolution] : undefined;
198
+ if (limit !== undefined && typeof value.duration === "number" && value.duration > limit) {
199
+ addIssue(ctx, "duration", `${String(value.resolution)} allows at most ${limit} seconds`);
200
+ }
201
+ }
202
+ function machgenInput(spec) {
203
+ return z.object({
204
+ prompt: promptField(spec),
205
+ ...frameFields(spec),
206
+ ...referenceFields(spec),
207
+ ...settingFields(spec),
208
+ }).superRefine((value, ctx) => {
209
+ const record = value;
210
+ const task = taskTypeOf(spec, record);
211
+ checkSettings(spec, record, task, ctx);
212
+ checkFrames(spec, record, ctx);
213
+ checkReferences(spec, record, task, ctx);
214
+ });
215
+ }
216
+ function frameSources(value) {
217
+ const first = text(value.first_frame_url);
218
+ const last = text(value.last_frame_url);
219
+ if (first === null && last !== null)
220
+ return { src_image_urls: [last], keyframe_indices: [-1] };
221
+ return { src_image_urls: [first, last].filter((url) => url !== null) };
222
+ }
223
+ function referenceSources(spec, value) {
224
+ const references = referencesOf(spec, value);
225
+ return Object.fromEntries(ALL_REFERENCES
226
+ .filter((kind) => references[kind].length > 0)
227
+ .map((kind) => [SOURCE_FIELDS[kind], references[kind]]));
228
+ }
229
+ function videoConfig(spec, value, task) {
230
+ const aspect = text(value.aspect_ratio);
231
+ return {
232
+ height: spec.resolutions[String(value.resolution)],
233
+ duration_secs: value.duration,
234
+ ...(aspect !== null && !(task === "I2V" && spec.framesFollowImage) ? { aspect_ratio: aspect } : {}),
235
+ ...(typeof value.generate_audio === "boolean" ? { audio: value.generate_audio } : {}),
236
+ ...spec.videoConfig,
237
+ };
238
+ }
239
+ function machgenRequest(apiId, spec, value) {
240
+ const task = taskTypeOf(spec, value);
241
+ return {
242
+ model: apiId,
243
+ task_type: task,
244
+ prompt: typeof value.prompt === "string" ? value.prompt : "",
245
+ video_config: videoConfig(spec, value, task),
246
+ ...(typeof value.seed === "number" ? { seed: value.seed } : {}),
247
+ ...(task === "I2V" ? frameSources(value) : {}),
248
+ ...(task === "R2V" ? referenceSources(spec, value) : {}),
249
+ };
250
+ }
251
+ export function machgenWire(apiId) {
252
+ const spec = MACHGEN_MODELS[apiId];
253
+ if (!spec)
254
+ return null;
255
+ return {
256
+ input: machgenInput(spec),
257
+ fixed: {},
258
+ wrap: (value) => machgenRequest(apiId, spec, value),
259
+ request: MachGenGenerateRequestSchema,
260
+ };
261
+ }
@@ -1,4 +1,4 @@
1
- const PROVIDERS = ["kie", "fal", "xai", "alibaba", "elevenlabs"];
1
+ const PROVIDERS = ["kie", "fal", "xai", "alibaba", "elevenlabs", "machgen"];
2
2
  function isProvider(value) {
3
3
  return PROVIDERS.includes(value);
4
4
  }
@@ -27,6 +27,8 @@ function providerPath(model) {
27
27
  return "alibaba.post.api.v1.services.aigc.multimodalGeneration.generation(request)";
28
28
  case "elevenlabs":
29
29
  return ELEVENLABS_PATHS[model.apiId] ?? "elevenlabs";
30
+ case "machgen":
31
+ return "machgen.api.v0.generate(request)";
30
32
  }
31
33
  }
32
34
  function placeholderRefs(refs) {
@@ -43,6 +43,8 @@ function rewrite(dialect, prompt, refs) {
43
43
  return natural(prompt, refs).replace(/@image([1-9]\d*)\b/giu, "reference image $1");
44
44
  case "natural_kinds":
45
45
  return natural(prompt, refs).replace(/@(image|audio)([1-9]\d*)(?![\w-])/giu, (_match, kind, index) => `reference ${kind.toLowerCase()} ${index}`);
46
+ case "natural_strip_frames":
47
+ return stripRefMentions(natural(prompt, refs), frames(refs));
46
48
  case "natural_source_video":
47
49
  return replaceNaturalAssetMentions(prompt, {
48
50
  imageRefs: refsFor(refs, "image"),
@@ -0,0 +1,48 @@
1
+ const SEEDANCE_DRAFT_COMPLETE_MODEL = "kie:bytedance/seedance-2-5-draft";
2
+ const SEEDANCE_DRAFT_SOURCE_MODEL = "bytedance/seedance-2-5";
3
+ function record(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
5
+ }
6
+ function seconds(value) {
7
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
8
+ }
9
+ async function readRecord(engine, artifactId, key) {
10
+ const result = await engine.metadata.artifacts.read(artifactId, key);
11
+ return result.ok ? record(result.value) : {};
12
+ }
13
+ async function draftArtifact(engine, taskId) {
14
+ for (const artifact of engine.artifacts.list()) {
15
+ if (artifact.kind !== "video")
16
+ continue;
17
+ const stored = await readRecord(engine, artifact.artifactId, "kie-task-id");
18
+ if (stored.taskId === taskId)
19
+ return artifact.artifactId;
20
+ }
21
+ return null;
22
+ }
23
+ function inputSeconds(input, hints) {
24
+ const stored = hints.inputDurationSeconds;
25
+ if (typeof stored === "number" && Number.isFinite(stored) && stored >= 0)
26
+ return stored;
27
+ const videos = input.reference_video_urls;
28
+ return Array.isArray(videos) && videos.length > 0 ? undefined : 0;
29
+ }
30
+ export async function seedanceDraftCostHints(engine, model, options) {
31
+ if (model !== SEEDANCE_DRAFT_COMPLETE_MODEL)
32
+ return {};
33
+ const taskId = typeof options.draft_task_id === "string" ? options.draft_task_id.trim() : "";
34
+ const artifactId = taskId ? await draftArtifact(engine, taskId) : null;
35
+ if (!artifactId)
36
+ return {};
37
+ const payload = await readRecord(engine, artifactId, "generation-payload");
38
+ const input = record(payload.input);
39
+ if (payload.model !== SEEDANCE_DRAFT_SOURCE_MODEL || input.draft !== true)
40
+ return {};
41
+ const generated = seconds((await readRecord(engine, artifactId, "original")).duration);
42
+ const durationSeconds = seconds(input.duration) ?? (generated === undefined ? undefined : Math.round(generated));
43
+ const inputDurationSeconds = inputSeconds(input, await readRecord(engine, artifactId, "generation-cost-hints"));
44
+ return {
45
+ ...(durationSeconds !== undefined ? { durationSeconds } : {}),
46
+ ...(inputDurationSeconds !== undefined ? { inputDurationSeconds } : {}),
47
+ };
48
+ }
@@ -4,6 +4,7 @@ import { FAL_ENDPOINT_REQUEST_SCHEMAS } from "@apicity/fal/zod";
4
4
  import { XaiVideoGenerateRequestSchema } from "@apicity/xai/zod";
5
5
  import { AlibabaMultimodalGenerationParametersSchema, AlibabaQwenImageEditStableModelSchema, AlibabaQwenImageGenerationStableModelSchema, } from "@apicity/alibaba/zod";
6
6
  import { ElevenLabsComposeMusicRequestSchema, ElevenLabsSoundGenerationRequestSchema, ElevenLabsTextToSpeechRequestSchema, ElevenLabsVideoToMusicRequestSchema, } from "@apicity/elevenlabs/zod";
7
+ import { machgenWire } from "./machgen.js";
7
8
  const KIE_VEO_MODELS = new Set(["veo3", "veo3_fast", "veo3_lite"]);
8
9
  const SUNO_CALLBACK_URL = "https://localhost/videobook/suno-callback";
9
10
  function objectShape(schema) {
@@ -157,5 +158,7 @@ export function wireSpec(parsed) {
157
158
  return alibabaWire(parsed.apiId);
158
159
  case "elevenlabs":
159
160
  return elevenLabsWire(parsed.apiId);
161
+ case "machgen":
162
+ return machgenWire(parsed.apiId);
160
163
  }
161
164
  }
@@ -1,6 +1,8 @@
1
1
  import { LIGHTNING_SCENE_MODEL, LIGHTNING_VIDEO_MODEL } from "./lightning-defaults.js";
2
2
  import { notebookCellTableRows } from "videobook-engine";
3
3
  import { notebookGridTag } from "videobook-engine/notebook-grid";
4
+ import { modelFrontierRankings } from "./media/frontier.js";
5
+ import { PROVIDER_ENV_KEYS } from "./media/adapters/index.js";
4
6
  import { listModels } from "./media/listing.js";
5
7
  import { notebookGenerationInputContract } from "./notebook-generation-contract.js";
6
8
  import { controlsForModel } from "./notebook-generation-controls.js";
@@ -265,6 +267,7 @@ const providerLabels = {
265
267
  xai: "xAI",
266
268
  alibaba: "Alibaba",
267
269
  elevenlabs: "ElevenLabs",
270
+ machgen: "MachGen",
268
271
  };
269
272
  function hasDualFrame(model) {
270
273
  const roles = new Set(model.slots.map((slot) => slot.role));
@@ -297,6 +300,8 @@ function generationAction(model) {
297
300
  ...(DEFAULT_MODELS.has(model.id) ? { defaultPriority: 0, isDefault: true } : {}),
298
301
  inputContract: notebookGenerationInputContract(model),
299
302
  controls: controlsForModel(model),
303
+ available: Boolean(process.env[PROVIDER_ENV_KEYS[model.provider]]?.trim()),
304
+ frontier: modelFrontierRankings(model.id),
300
305
  };
301
306
  }
302
307
  export function listNotebookActions(options = {}) {
@@ -35,14 +35,19 @@ export function readOnePasswordSettings(env) {
35
35
  export function redactToken(text, token) {
36
36
  return token ? text.split(token).join("***") : text;
37
37
  }
38
- export function planReferences(env, managedKeys, vault, vaultTitles) {
38
+ /** Every vault item titled like an environment variable fills that variable, except the vault settings themselves. */
39
+ function isVaultEnvTitle(title) {
40
+ return ENV_NAME.test(title) && title !== OP_VAULT_SETTING && title !== OP_TOKEN_SETTING;
41
+ }
42
+ export function planReferences(env, vault, vaultTitles) {
39
43
  const explicit = Object.entries(env)
40
44
  .filter((entry) => isOpReference(entry[1]))
41
45
  .map(([key, reference]) => [key, reference.trim()]);
42
46
  const covered = new Set(explicit.map(([key]) => key));
43
47
  const convention = vault
44
- ? managedKeys
45
- .filter((key) => !covered.has(key) && !env[key]?.trim() && vaultTitles.has(key))
48
+ ? [...vaultTitles]
49
+ .filter((key) => isVaultEnvTitle(key) && !covered.has(key) && !env[key]?.trim())
50
+ .sort()
46
51
  .map((key) => [key, vaultReference(vault, key)])
47
52
  : [];
48
53
  return { explicit, convention };
@@ -98,8 +103,9 @@ export function listVaultItemTitles(vault, env, token) {
98
103
  if (!Array.isArray(parsed))
99
104
  throw new Error("op item list did not return a list");
100
105
  return new Set(parsed.flatMap((item) => {
101
- const title = item && typeof item === "object" ? item.title : undefined;
102
- return typeof title === "string" ? [title] : [];
106
+ const { title, category } = item && typeof item === "object" ? item : {};
107
+ // SSH keys carry no password field, so op://<vault>/<title>/password cannot resolve them.
108
+ return typeof title === "string" && category !== "SSH_KEY" ? [title] : [];
103
109
  }));
104
110
  }
105
111
  function injectBatch(entries, env, token) {
@@ -169,13 +175,13 @@ function emptyResolution(settings) {
169
175
  error: null,
170
176
  };
171
177
  }
172
- export function resolveOnePasswordEnv(env, managedKeys) {
178
+ export function resolveOnePasswordEnv(env) {
173
179
  const settings = readOnePasswordSettings(env);
174
180
  const hasReferences = Object.values(env).some((value) => isOpReference(value));
175
181
  if (!hasReferences && !settings.vault)
176
182
  return emptyResolution(settings);
177
183
  const listing = vaultTitles(settings, env);
178
- const plan = planReferences(env, managedKeys, settings.vault, listing.titles);
184
+ const plan = planReferences(env, settings.vault, listing.titles);
179
185
  const result = resolveEntries([...plan.explicit, ...plan.convention], env, settings.token.value);
180
186
  for (const [key] of plan.explicit) {
181
187
  const value = result.values.get(key);
@@ -11,7 +11,7 @@ export function parseProviderTaskStatus(value) {
11
11
  if (!value || typeof value !== "object" || Array.isArray(value))
12
12
  return null;
13
13
  const record = value;
14
- if (record.provider !== "kie" && record.provider !== "fal")
14
+ if (record.provider !== "kie" && record.provider !== "fal" && record.provider !== "machgen")
15
15
  return null;
16
16
  return {
17
17
  provider: record.provider,
@@ -1,3 +1,4 @@
1
+ import { MEDIA_TASK_TYPES } from "./media/adapters/types.js";
1
2
  import { parseModelId } from "./media/model-id.js";
2
3
  import { legacyMediaForTaskType } from "./media/legacy-task-types.js";
3
4
  import { readProviderTaskRecord } from "./provider-task-record.js";
@@ -22,14 +23,16 @@ export async function readProviderTaskStatus(engine, artifactId, hints = {}) {
22
23
  return null;
23
24
  const model = firstString(endpoint, payload.model, catalogModel?.apiId, saved?.meta.apiModel, saved?.meta.model, hints.model);
24
25
  const taskId = firstString(saved?.taskId, storedId.taskId, hints.taskId);
26
+ const machgenTask = saved?.taskType === MEDIA_TASK_TYPES.machgen;
25
27
  if (saved && /^(?:xai|alibaba)_/u.test(saved.taskType))
26
28
  return null;
27
- if (saved && saved.taskType !== "media_kie" && saved.taskType !== "isolate_vocals"
29
+ if (saved && !machgenTask && saved.taskType !== "media_kie" && saved.taskType !== "isolate_vocals"
28
30
  && !saved.taskType.startsWith("fal_") && legacyMediaForTaskType(saved.taskType) === null)
29
31
  return null;
30
32
  const provider = endpoint || saved?.taskType.startsWith("fal_") ? "fal"
31
- : catalogModel?.provider ?? hints.provider ?? (storedId.taskId || saved ? "kie" : null);
32
- if (provider !== "kie" && provider !== "fal")
33
+ : machgenTask ? "machgen"
34
+ : catalogModel?.provider ?? hints.provider ?? (storedId.taskId || saved ? "kie" : null);
35
+ if (provider !== "kie" && provider !== "fal" && provider !== "machgen")
33
36
  return null;
34
37
  return { provider, model, taskId: taskId === "queued" ? null : taskId };
35
38
  }
@@ -146,6 +146,8 @@ function isBillable(provider, path, method) {
146
146
  return path.includes("/services/aigc/");
147
147
  if (provider === "elevenlabs")
148
148
  return /\/(text-to-speech|speech-to-text|speech-to-speech|sound-generation|audio-isolation|music)(\/|$)/u.test(path);
149
+ if (provider === "machgen")
150
+ return path === "/api/v0/generate";
149
151
  return /\/(videos|images)(\/|$)/u.test(path);
150
152
  }
151
153
  async function payloadOf(input, init) {
@@ -177,7 +179,7 @@ function remoteId(body, url, method) {
177
179
  ?? (/\/(requests|tasks|jobs|videos)\/([^/]+)(?:\/status)?$/u.exec(url.pathname)?.[2] ?? null);
178
180
  if (method === "GET" && pathId)
179
181
  return decodeURIComponent(pathId);
180
- return text(data.taskId) ?? text(data.task_id) ?? text(body.request_id) ?? text(body.job_id)
182
+ return text(data.taskId) ?? text(data.task_id) ?? text(body.task_id) ?? text(body.request_id) ?? text(body.job_id)
181
183
  ?? text(output.task_id) ?? text(body.id);
182
184
  }
183
185
  function responseStatus(body, accepted) {
@@ -247,7 +249,7 @@ export function providerUsageFetch(provider, transport = fetch) {
247
249
  catch {
248
250
  void 0;
249
251
  }
250
- const activity = activityFor(model, url.pathname, context.operation);
252
+ const activity = provider === "machgen" ? "video" : activityFor(model, url.pathname, context.operation);
251
253
  entry = {
252
254
  id: randomUUID(), provider, model, activity, status: "pending",
253
255
  tokenBased: /\/(responses|completions|embeddings)$/u.test(url.pathname) || rateFor(provider, model)?.kind === "tokens",
@@ -270,7 +272,7 @@ export function providerUsageFetch(provider, transport = fetch) {
270
272
  const body = json ? object(await response.clone().json()) : {};
271
273
  const externalId = remoteId(body, url, method);
272
274
  if (entry) {
273
- const accepted = !!externalId && (provider === "kie" || provider === "fal" || /videos|tasks/u.test(url.pathname) || !!object(body.output).task_id);
275
+ const accepted = !!externalId && (provider === "kie" || provider === "fal" || provider === "machgen" || /videos|tasks/u.test(url.pathname) || !!object(body.output).task_id);
274
276
  const status = response.ok ? responseStatus(body, accepted) : "failed";
275
277
  safeRecord(engine, { ...entry, ...(externalId ? { externalId } : {}), ...observedPatch(provider, entry, body, status) });
276
278
  }
package/dist/queue.js CHANGED
@@ -443,6 +443,9 @@ async function createBookRunner(bookId) {
443
443
  const runner = engine.jobs.queue.createRunner({
444
444
  concurrency: 5,
445
445
  pollIntervalMs: 250,
446
+ // The engine default lease is 30s. Remotion can stall the event loop
447
+ // longer than that, and the reaper then fails the subtitle render.
448
+ leaseMs: 10 * 60_000,
446
449
  preferredJobTypes: SEARCH_REFERENCE_JOB_TYPES,
447
450
  resolveHandler: makeWrappedResolver(bookId, engine),
448
451
  });
@@ -1088,7 +1088,7 @@ const CANONICAL_COMMANDS = [
1088
1088
  },
1089
1089
  {
1090
1090
  command: "/transcribe",
1091
- description: "Transcribe audio using ElevenLabs",
1091
+ description: "Open a chat review to transcribe a video or audio clip",
1092
1092
  example: "/transcribe @a1",
1093
1093
  category: "action",
1094
1094
  icon: "Mic",
@@ -6,6 +6,7 @@ import { DEFAULT_IMAGE_EDIT_MODEL_ID, findImageEditModel } from "../image-edit-m
6
6
  import { tryResolveModel } from "../media/descriptor.js";
7
7
  import { estimateGeneration } from "../media/cost.js";
8
8
  import { checkSeedanceEditSource, normalizeRefs } from "../media/refs.js";
9
+ import { seedanceDraftCostHints } from "../media/seedance-draft.js";
9
10
  import { REF_ROLES } from "../media/types.js";
10
11
  const GENERATION_TOOLS = new Set(["generate_image", "generate_video", "generate_audio"]);
11
12
  const REWRITE_MODEL = "gpt-5.6-sol";
@@ -70,12 +71,13 @@ async function estimateMedia(engine, input) {
70
71
  const sourceError = await checkSeedanceEditSource(engine, model, refs, input.mode);
71
72
  if (sourceError)
72
73
  return unavailable(sourceError);
74
+ const hints = { ...await mediaHints(engine, refs), ...await seedanceDraftCostHints(engine, model.id, options) };
73
75
  const result = await estimateGeneration(model, {
74
76
  prompt: text(input.prompt),
75
77
  refUrls: placeholderRefs(refs),
76
78
  options,
77
79
  mode: text(input.mode),
78
- }, await mediaHints(engine, refs));
80
+ }, hints);
79
81
  return result.ok ? result.estimate : unavailable(result.error);
80
82
  }
81
83
  async function estimateImageEdit(input) {
@@ -15,6 +15,7 @@ const KEY_REGISTRY = {
15
15
  FAL_API_KEY: { provider: "fal", label: "FAL" },
16
16
  FAL_ADMIN_API_KEY: { provider: "fal", label: "FAL admin" },
17
17
  DASHSCOPE_API_KEY: { provider: "dashscope", label: "DashScope" },
18
+ MACHGEN_API_KEY: { provider: "machgen", label: "MachGen" },
18
19
  B2_ACCESS_KEY_ID: { provider: "b2", label: "Backblaze B2" },
19
20
  B2_SECRET_ACCESS_KEY: { provider: "b2", label: "Backblaze B2" },
20
21
  B2_REGION: { provider: "b2", label: "Backblaze B2" },
@@ -35,6 +36,7 @@ const ROW_ORDER = [
35
36
  "FAL_API_KEY",
36
37
  "FAL_ADMIN_API_KEY",
37
38
  "DASHSCOPE_API_KEY",
39
+ "MACHGEN_API_KEY",
38
40
  "B2_ACCESS_KEY_ID",
39
41
  "B2_SECRET_ACCESS_KEY",
40
42
  "B2_REGION",